Mirror dirty files between matching Windows and WSL Git repositories.
Project description
wdsync
wdsync is a cross-environment (WSL <-> Windows) Python CLI for mirroring the dirty working tree
between matching WSL and Windows Git repositories without requiring a commit,
push, or pull cycle.
It is built for the workflow where:
- your primary editing happens to be on Windows for whatever reason (Flutter, VS Studio, .NET development, etc.)
- your backend, scripts, containers, or Linux tooling run in WSL either for prod or testing parity
- you want the WSL repo to mirror the current Windows dirty-set (staged and unstaged changes) on demand
The key design choice is that wdsync reads each source repo with the git
implementation native to that repo (git on WSL, git.exe on Windows), so
the sync set matches what that environment considers dirty rather than what the
other side infers through translated paths.
What It Does
From inside a git repo, wdsync will:
- auto-detect the repo identity via remote URL or root commit SHA
- initialize local wdsync state with
wdsync init - discover and link the peer repo via RPC with
wdsync connect - establish the connection on both repos from a single
connectcall - remove only the saved peer link with
wdsync disconnect - remove local wdsync state entirely with
wdsync deinit - fetch (pull from peer into the local repo) with
wdsync fetch - send (push from the local repo to the peer) with
wdsync send - show a unified status of both repos with
wdsync status
Note: It includes tracked unstaged files, tracked staged files, and untracked files, including nested files in untracked directories. Deleted files detected in the source are propagated to the destination. If a deletion fails due to permissions,
wdsyncprompts to retry withsudo. Files with local changes in the destination are never deleted. It does not yet do patch-apply checks, staging/index mirroring, or global config.
Prerequisites
Get git available in WSL and Windows (git.exe), plus rsync, wslpath,
Python 3.11+, and either uv or pip; the source repo must be reachable as
/mnt/<drive>/....
wdsync does not install rsync for you. It must already be available:
- WSL/Linux: install it system-wide, for example
sudo apt install rsync - Windows:
wdsyncuseswsl.exe --exec rsync, so installrsyncinside the target WSL distro and verify withwsl.exe --exec rsync --version
Install
- Read the detailed Setup guide here | Recommended install path:
uv tool install wdsync
# or Alternative install path with `pip` after python venv:
python -m pip install wdsync
Per-Project Config
Running wdsync init inside a git repo creates:
- A
.wdsyncmarker file at the repo root (added to.git/info/exclude) - A
.git/wdsync/config.jsonwith the repo's identity (remote URL + root commit SHA)
No manual path configuration needed. The identity is used to match repos
during wdsync connect.
Quick Start
# On each side (WSL and Windows), inside the repo:
wdsync init
# From either side, discover and link the peer:
wdsync connect
# After one successful connect, both repos are configured.
# Daily use from either side:
wdsync status # see both repos' dirty files, conflicts, risk
wdsync fetch # pull from peer into local repo
wdsync send # push from local repo to peer
If you want to contribute or work on the codebase itself, use:
- DEVELOPER_SETUP for contributor environment setup
Additional references:
- CHANGELOG for release history
Project Layout
wdsync/
├── docs/ # Design documents and roadmap
├── scripts/ # Repo-local shell wrapper examples
│ ├── bash/
│ └── fish/
├── src/wdsync/ # Published package source
│ ├── core/ # Foundation: models, config, runner, exceptions, logging
│ ├── git/ # Git state readers and porcelain parsing
│ ├── sync/ # Sync pipeline: planner, engine, deleter, manifest, conflicts
│ ├── rpc/ # RPC client, peer discovery, connect flow, request handlers
│ ├── output/ # Human-readable and JSON formatters
│ ├── cli/ # Typer CLI command definitions
│ └── shell/ # Shell completion and helper installation
└── tests/ # Mirrors src/ structure with unit and integration tests
Sync Rules
| Scenario | Behavior |
|---|---|
| File modified in source only | Copied to destination |
| File modified in destination only | Not touched (destination changes are preserved) |
| File modified on both sides | Conflict — skipped unless --force is used |
| File deleted in source (tracked) | Deleted from destination |
| File deleted in source (untracked, previously synced) | Deleted from destination via pair-owned manifest tracking |
| File deleted then restored in source | Restored in destination via git restore |
| Deleted file has local changes in destination | Skipped to avoid data loss |
| Permission denied on deletion (WSL path) | Prompts for sudo retry |
| Permission denied on deletion (Windows path) | Skipped — sudo has no authority over NTFS ACLs |
Path traversal attempt (../../etc/passwd) |
Blocked |
| Empty parent directory after deletion | Pruned automatically |
| Source staged file synced to destination | Copied as content only — not staged in destination |
| Untracked directory in source | Expanded to leaf file paths and synced |
Commands
| Command | JSON Output | Purpose |
|---|---|---|
wdsync init |
No | Create .wdsync and .git/wdsync/config.json for this repo. |
wdsync connect |
No | Discover peer via RPC, verify identity, and save the link on both repos. |
wdsync disconnect |
No | Remove the saved peer from config. |
wdsync deinit |
No | Remove local wdsync state and restore the repo to a pre-init state. |
wdsync fetch |
Yes, with wdsync fetch --json |
Pull peer dirty tree into the local repo. Use --force for conflicts. |
wdsync send |
Yes, with wdsync send --json |
Push local dirty tree to the peer. Use --force for conflicts. |
wdsync status |
Yes, with wdsync status --json |
Unified view of both repos, conflicts, and risk. |
wdsync shell install |
No | Install optional shell helpers and completions. |
Git Status Labels Passed
| [unstaged] [ M] | [staged] [M ] | [both] [MM] | [new] [??] | [added] [A ] | [added+mod] [AM] | [renamed] [R ] | [copied] [C ] | [deleted] [ D] | fallback [changed] |
Shell Integration
The CLI itself is shell-agnostic once installed. Optional shell helpers can be installed with:
wdsync shell install
Override shell detection when needed:
wdsync shell install --shell fish
wdsync shell install --shell bash
wdsync shell install --shell zsh
What shell install does:
- auto-detects
fish,bash, orzsh - installs completion assets
- installs a
wdsync-inithelper wrapper - installs fish function delegates when using fish
The scripts/ directory contains thin repo-local wrapper examples that
delegate to the installed CLI or fall back to uv run.
scripts/bash/contains executable shell wrappersscripts/fish/contains fish functions- the bash wrappers also work fine as standalone executable helpers from zsh
Important Behavior Notes
Each side reads its own dirty state using its native git (git on WSL,
git.exe on Windows). connect is bilateral: one successful wdsync connect
stores peer configuration on both repos, and either repo can then run
status, fetch, or send. Repo inspection and peer-side delete/restore run
natively through RPC, while file transfer still uses rsync over translated peer
paths. Conflicts (files dirty on both sides) are detected and blocked by
default — use --force to override. State lives under .git/wdsync/, with a
repo-root .wdsync marker for visibility. disconnect removes only the peer
link; deinit removes local wdsync state entirely. See the Sync Rules table
above for full details.
Limitations
wdsync currently depends on WSL/Windows path interop: Windows repos must be
reachable from WSL as /mnt/<drive>/..., and WSL repos must be reachable from
Windows through \\wsl.localhost\... path translation. connect can be
initiated from either side. File transfer still depends on translated paths via
rsync, but repo inspection and peer-side delete/restore are RPC-native. wdsync
does not support empty repos (at least one commit is required for identity
resolution). It does not yet run patch-apply checks or support post-sync hooks.
See ARCHITECTURE.md for design decisions and IN_DEVELOPMENT.md for the roadmap.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file wdsync-0.6.0.tar.gz.
File metadata
- Download URL: wdsync-0.6.0.tar.gz
- Upload date:
- Size: 98.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.3 {"installer":{"name":"uv","version":"0.11.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8b232471a0e66ce15a3b9fa6f1f352c5a859a81974ec470dfda7ebbf624bf153
|
|
| MD5 |
c6636c7a748f384806c3b45f151dfecb
|
|
| BLAKE2b-256 |
1f56dde8826a931169d8c8157ad610205e4ae309250e3372262e7b5789cc8bb1
|
File details
Details for the file wdsync-0.6.0-py3-none-any.whl.
File metadata
- Download URL: wdsync-0.6.0-py3-none-any.whl
- Upload date:
- Size: 62.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.3 {"installer":{"name":"uv","version":"0.11.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
67bd7db23d6eefac7d610773f8845cac07ef76c8683f8fcbd3669cb8c1822f1a
|
|
| MD5 |
5ec6d08dce2045aeacc75dbdfdb70d8c
|
|
| BLAKE2b-256 |
a5d393a0167e3027afadf9c3004fef15594e12c1eb399bfa8fa5174c23c875c0
|