Skip to content

aleixrodriala/GithubDesktopWSL

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

7 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

GitHub Desktop WSL

Think of it like the VS Code Remote WSL extension but for GitHub Desktop. A fork of GitHub Desktop that makes WSL repositories work properly — 6-27x faster git operations, working SSH keys, and no more CRLF issues.

Download the latest release — installs side-by-side with official GitHub Desktop

The problem

Official GitHub Desktop can't handle repos inside WSL. When you open a \\wsl.localhost\... path:

  • Git commands are unusably slow — Desktop runs Windows git.exe, which accesses WSL files through the 9P protocol. Every file stat, read, and open is a round-trip across the VM boundary.
  • SSH keys don't work — Desktop injects a Windows-only SSH_ASKPASS binary that breaks SSH inside WSL.
  • File operations fail — Checking merge/rebase state, reading diffs, writing .gitignore — all go through 9P and are either slow or broken.
  • Deleting repos fails — Windows Recycle Bin doesn't support WSL UNC paths.

The solution

This fork runs a lightweight daemon inside WSL that executes git and file operations natively. Desktop talks to it over TCP instead of going through 9P.

Official Desktop (slow path):
  Desktop → git.exe → Windows kernel → 9P → VM boundary → WSL → ext4
  (every file operation is a round-trip)

This fork (fast path):
  Desktop → TCP → daemon → git (native) → ext4
  (one round-trip per command, git has direct disk access)

The daemon is bundled inside the installer. When you open a WSL repo, it's deployed and started automatically. There's nothing to configure.

Performance

6x-27x faster across all git operations compared to official Desktop on WSL repos.

Git operations by repo size

OperationSmall (20 files)Medium (333 files)Large (2,372 files)
Daemongit.exeDaemongit.exeDaemongit.exe
git status2 ms43 ms (20x)3 ms41 ms (15x)7 ms41 ms (6x)
git log -202 ms41 ms (24x)3 ms41 ms (16x)3 ms41 ms (14x)
git diff HEAD~12 ms41 ms (25x)2 ms41 ms (20x)3 ms43 ms (14x)
git branch -a2 ms44 ms (23x)2 ms40 ms (18x)3 ms51 ms (20x)
git rev-parse2 ms42 ms (27x)2 ms40 ms (26x)2 ms41 ms (27x)
for-each-ref2 ms42 ms (26x)3 ms40 ms (12x)4 ms41 ms (11x)
Average speedup24x18x16x

Real Desktop workflows

These are the actual sequences Desktop runs during common operations:

Workflow This fork Official Desktop Speedup
Open / switch to a repo 16 ms 173 ms 11x
Populate branch list + dates 6 ms 88 ms 14x
View last commit diff 4 ms 44 ms 10x
Pre-fetch checks 3 ms 82 ms 26x

Benchmarks: 7 iterations, median, WSL2 on Windows 11. The "open repo" workflow includes git status, for-each-ref, 4 pathExists checks, readFile, and git log.

Why the difference is so large

git.exe running on a WSL repo has a ~40ms floor for any operation — that's the cost of launching git.exe through 9P and resolving paths across the VM boundary. Our daemon has ~2ms overhead (TCP round-trip + message framing). The actual git work is the same; the difference is entirely in how files are accessed.

Install

  1. Download GitHubDesktopWSLSetup-x64.exe
  2. Run the installer
  3. Open any WSL repository

That's it. The app:

  • Detects WSL paths automatically (\\wsl.localhost\... or \\wsl$\...)
  • Deploys the daemon to ~/.local/bin/ in your WSL distro on first use
  • Starts the daemon via --daemonize (forks to background)
  • Reconnects automatically if the daemon crashes
  • Falls through to normal Desktop behavior for Windows repos

SmartScreen may warn on first install (the fork is unsigned). Click "More info" then "Run anyway".

How it works

Components

wsl-git-daemon (C, ~550 lines, zero dependencies) A persistent daemon that runs inside WSL. Listens on TCP 127.0.0.1 on a random port. Handles:

  • Git commands — forks git with proper pipes for stdout/stderr/stdin
  • File operations — read, write, stat, pathExists, unlink via direct syscalls
  • Security — token-based auth, localhost-only connections

wsl.ts (TypeScript, ~420 lines) The Desktop-side client. Responsibilities:

  • Path detection — identifies \\wsl.localhost\... and \\wsl$\... paths, extracts distro name
  • Lifecycle management — deploy binary, start/restart daemon, health check
  • Protocol client — binary length-prefixed frames over TCP
  • Drop-in wrapperswslReadFile(), wslPathExists(), etc. that route to daemon for WSL paths and fall through to native fs for Windows paths

core.ts patch (1 check) All git commands in Desktop flow through git() in core.ts. A single if (isWSLPath(path)) routes WSL repos through the daemon while leaving Windows repos completely untouched.

Wire protocol

Frame: [type: 1 byte][length: 4 bytes big-endian][payload: N bytes]

Client → Daemon:
  INIT  (0x01)  JSON { token, cmd, args, cwd, stdin, path }
  STDIN (0x02)  Raw bytes (for writeFile content, empty = EOF)

Daemon → Client:
  STDOUT      (0x03)  Raw bytes (git output / file content)
  STDERR      (0x04)  Raw bytes
  EXIT        (0x05)  4-byte exit code
  ERROR       (0x06)  UTF-8 error message
  STAT_RESULT (0x07)  JSON { exists, size, isDir }

Daemon lifecycle

User opens WSL repo
  → Desktop detects \\wsl.localhost\Ubuntu\...
  → Extracts distro name ("Ubuntu")
  → Tries to connect to daemon (reads /tmp/wsl-git-daemon.info via UNC path)
  → If not running:
      → Deploys binary: wsl.exe -d Ubuntu -e sh -c "cp ... ~/.local/bin/ && chmod 755 ..."
      → Starts daemon: wsl.exe -d Ubuntu -e sh -c "~/.local/bin/wsl-git-daemon --daemonize"
      → Daemon forks, writes info file, parent exits, wsl.exe returns
      → Desktop reads info file, connects to daemon
  → All git/file operations go through daemon
  → If connection fails mid-session → auto-restart

Staying current with upstream

This project doesn't diverge from upstream GitHub Desktop. It maintains a set of patch files (in patches/) that are applied on top of each upstream release:

  1. CI checks for new upstream release-* tags every 6 hours
  2. Clones desktop/desktop at the new release tag
  3. Applies patches/*.patch via git apply
  4. Builds the daemon + app, publishes a new release
  5. If patches fail to apply → opens a GitHub Issue for manual resolution

The patches are designed to be minimal and conflict-resistant: one new file (wsl.ts), one new directory (wsl-daemon/), and small surgical changes to existing files (mostly import swaps). All patches are plain text and easy to review.

Files changed

NEW   wsl-daemon/daemon.c                  Persistent daemon
NEW   wsl-daemon/Makefile                  Build script
NEW   app/src/lib/wsl.ts                   Client, lifecycle, wrappers
PATCH app/src/lib/git/core.ts              Git routing (1 if-block)
PATCH app/src/main-process/main.ts         WSL delete handler
PATCH app/src/models/repository.ts         isWSL getter
PATCH app/src/lib/git/diff.ts              Import swap: wslReadFile
PATCH app/src/lib/git/rebase.ts            Import swap: wslReadFile, wslPathExists
PATCH app/src/lib/git/cherry-pick.ts       Import swap: wslReadFile, wslPathExists
PATCH app/src/lib/git/merge.ts             Import swap: wslPathExists
PATCH app/src/lib/git/description.ts       Import swap: wslReadFile, wslWriteFile
PATCH app/src/lib/git/gitignore.ts         WSL-aware read/write/unlink
PATCH app/src/lib/git/submodule.ts         Import swap: wslPathExists
PATCH app/src/lib/stores/app-store.ts      Import swap: wslPathExists
PATCH app/package.json                     Branding: "GitHub Desktop WSL"
PATCH script/dist-info.ts                  Update URL, app ID
PATCH script/build.ts                      Bundle daemon binary
PATCH script/package.ts                    Skip code signing
NEW   .github/workflows/sync-upstream.yml  Auto-sync with upstream
NEW   .github/workflows/build-release.yml  Build and publish releases

Most patches are one-line import changes — swapping readFile from fs/promises with wslReadFile from wsl.ts. These are unlikely to conflict with upstream changes.

Building from source

# 1. Build daemon (in WSL)
cd wsl-daemon && make

# 2. Install dependencies (on Windows)
yarn install

# 3. Build
yarn build:dev     # development
yarn build:prod    # production

# 4. Package installer (production only)
SKIP_CODE_SIGNING=1 yarn package

Related projects

  • wsl-git-shim — A simpler, zero-fork approach: replaces Desktop's bundled git.exe with a shim that routes WSL paths to wsl.exe -e git. Works with official Desktop but slower (~40ms overhead per command from spawning wsl.exe) and doesn't support file operations.

License

MIT

Credits

Based on GitHub Desktop by GitHub, Inc.

WSL support by @aleixrodriala.