Skip to content

MathiasCodes/winstow

Repository files navigation

Winstow

License: MIT

Winstow is a Windows-native symlink farm manager inspired by GNU Stow. It helps you manage your dotfiles, configuration files, and software packages by creating symbolic links from a central stow directory to a target directory (typically your home directory).

Disclaimer

I created Winstow to replicate my GNU Stow dotfile management workflow from Linux on Windows - keeping all my configuration files in a single git repository and symlinking them as needed.

⚠️ Note: Winstow is currently in active development and may be unstable. The API is subject to change without prior notice. Please use it with caution and make backups of your files before using it.

I'm still learning Rust, so the code may not be the most idiomatic or efficient. Suggestions for improvement are welcome.

⚠️ Winstow is not a 1:1 port of GNU Stow and does not guarantee identical range of functionality or behavior.

Why Winstow?

  • Windows-Native: Built specifically for Windows with proper symlink support
  • GNU Stow Compatible: Familiar interface if you've used GNU Stow
  • Directory Folding: Intelligent directory symlinking reduces link count
  • Conflict Resolution: --adopt and --override for handling existing files
  • Pattern Matching: --ignore and --defer for flexible file management
  • Configuration Files: Set defaults with .winstowrc
  • Small & Fast: Single ~600KB binary
  • Safe: Dry-run mode to preview changes before applying

Prerequisites

To create symbolic links on Windows, you need one of the following:

  1. Developer Mode enabled (Windows 10/11) - Recommended

    • Settings → Update & Security → For developers → Developer Mode
  2. Administrator privileges

    • Run winstow from an elevated command prompt

Installation

Option 1: Download Binary

Download the latest release from GitHub Releases and add it to your PATH.

Option 2: Build from Source

# Clone the repository
git clone https://github.com/MathiasCodes/winstow.git
cd winstow

# Build release binary
cargo build --release

# Binary will be at target/release/winstow.exe

Option 3: Package Managers

# WinGet
winget install winstow

Quick Start

Basic Usage

# Stow a package (create symlinks)
winstow mypackage

# Unstow a package (remove symlinks)
winstow -D mypackage

# Restow a package (refresh symlinks)
winstow -R mypackage

# Dry-run to see what would happen
winstow -n mypackage

# Verbose output
winstow -v mypackage

Directory Structure

# Stow directory (contains all your dotfile packages)
C:\Users\USER\Dotfiles\
  ├── Git\
  │   └── .gitconfig
  ├── Git-Bash\
  │   ├── .bashrc
  │   └── .inputrc
  └── lazygit\
      └── AppData\
          └── Local\
              └── lazygit\
                  └── config.yaml

# Target directory (where symlinks are created, typically your home directory)
# Note: lazygit uses directory folding - a single directory symlink instead of individual file symlinks
C:\Users\USER\
  ├── .gitconfig -> C:\Users\USER\Dotfiles\Git\.gitconfig
  ├── .bashrc -> C:\Users\USER\Dotfiles\Git-Bash\.bashrc
  ├── .inputrc -> C:\Users\USER\Dotfiles\Git-Bash\.inputrc
  └── AppData\
      └── Local\
          └── lazygit -> C:\Users\USER\Dotfiles\lazygit\AppData\Local\lazygit

Command-Line Interface

Actions

Flag Long Form Description
-S (optional) --stow Stow packages (default action)
-D --delete Unstow (delete) packages
-R --restow Restow packages (unstow then stow)

Options

Flag Long Form Description
-d DIR --dir DIR Stow directory (default: current directory)
-t DIR --target DIR Target directory (default: home directory)
-v --verbose Enable verbose output
-n --dry-run Preview changes without applying them
--adopt Move conflicting files into package (stow/restow only)
--override Remove conflicting files (stow/restow only, destructive)
--ignore PATTERN Skip files matching pattern (stow/restow only)
--defer PATTERN Skip files matching pattern if they already exist in target (stow/restow only)
-h --help Show help message
-V --version Show version

Examples

Managing Dotfiles

PowerShell:

# Set up your stow directory
mkdir $env:USERPROFILE\Dotfiles
cd $env:USERPROFILE\Dotfiles
mkdir Git
mkdir Git-Bash

# Move your dotfiles into the packages
move $env:USERPROFILE\.gitconfig Git\
move $env:USERPROFILE\.bashrc Git-Bash\
move $env:USERPROFILE\.inputrc Git-Bash\

# Stow the packages
winstow -d $env:USERPROFILE\Dotfiles -t $env:USERPROFILE Git
winstow -d $env:USERPROFILE\Dotfiles -t $env:USERPROFILE Git-Bash

# Now .gitconfig, .bashrc, and .inputrc are symlinks to your Dotfiles packages

Git Bash:

# Set up your stow directory
mkdir -p $USERPROFILE/Dotfiles
cd $USERPROFILE/Dotfiles
mkdir Git
mkdir Git-Bash

# Move your dotfiles into the packages
mv $USERPROFILE/.gitconfig Git/
mv $USERPROFILE/.bashrc Git-Bash/
mv $USERPROFILE/.inputrc Git-Bash/

# Stow the packages
winstow -d $USERPROFILE/Dotfiles -t $USERPROFILE Git
winstow -d $USERPROFILE/Dotfiles -t $USERPROFILE Git-Bash

# Now .gitconfig, .bashrc, and .inputrc are symlinks to your Dotfiles packages

Handling Conflicts

# If files already exist in target, you'll get an error
winstow mypackage
# Error: Conflict: C:\Users\You\.gitconfig already exists

# Option 1: Adopt the existing file into the package
winstow --adopt mypackage
# Moves .gitconfig into mypackage/, then creates symlink

# Option 2: Override (remove) the existing file
winstow --override mypackage
# Removes .gitconfig, then creates symlink (destructive!)

Using Ignore and Defer Patterns

# Ignore backup files and OS metadata (always skip these)
winstow --ignore "*.bak" --ignore ".DS_Store" --ignore "Thumbs.db" mypackage

# Ignore entire directories
winstow --ignore "node_modules" mypackage

# Defer allows another package to manage shared files
# If .bashrc already exists, skip it; otherwise, stow it
winstow --defer ".bashrc" package-a

# Later, package-b can manage .bashrc
winstow package-b  # This will stow .bashrc from package-b

Difference between --ignore and --defer:

  • --ignore: Always skip files matching the pattern (e.g., temporary files, build artifacts)
  • --defer: Skip files matching the pattern only if they already exist in the target directory (useful for shared configuration files managed by different packages)

Restowing After Updates

PowerShell:

# After updating your dotfiles repository
cd $env:USERPROFILE\Dotfiles
git pull

# Refresh symlinks
winstow -R -d $env:USERPROFILE\Dotfiles -t $env:USERPROFILE Git

Git Bash:

# After updating your dotfiles repository
cd $USERPROFILE/Dotfiles
git pull

# Refresh symlinks
winstow -R -d $USERPROFILE/Dotfiles -t $USERPROFILE Git

Dry-Run Mode

PowerShell:

# See what would happen without making changes
winstow -n -v Git

# Output:
# === DRY RUN MODE - No changes will be made ===
# [DRY RUN] Create file link: .gitconfig -> ..\Dotfiles\Git\.gitconfig
# Would stow 1 package(s)

Git Bash:

# See what would happen without making changes
winstow -n -v Git

# Output:
# === DRY RUN MODE - No changes will be made ===
# [DRY RUN] Create file link: .gitconfig -> ..\Dotfiles\Git\.gitconfig
# Would stow 1 package(s)

Configuration File

Create a .winstowrc file in one of these locations:

  1. Current directory: ./.winstowrc
  2. Home directory: %USERPROFILE%\.winstowrc
  3. AppData: %APPDATA%\winstow\config.toml

Example Configuration

# Default stow directory
default-dir = "C:\\stow"

# Default target directory  
default-target = "C:\\Users\\YourName"

# Default ignore patterns
ignore = ["*.bak", ".DS_Store", "Thumbs.db", "desktop.ini"]

# Default defer patterns
defer = ["*.lock"]

# Enable verbose mode by default
verbose = false

CLI arguments always override config file settings.

Directory Folding

winstow implements directory folding (inspired by GNU Stow) for efficiency:

Folding (Creating a Single Directory Symlink)

# Package structure:
C:\Users\USER\Dotfiles\mypackage\AppData\Local\app\settings.json

# If C:\Users\USER\AppData\Local\app\ doesn't exist, winstow creates:
C:\Users\USER\AppData\Local\app -> C:\Users\USER\Dotfiles\mypackage\AppData\Local\app

Unfolding (Expanding When Needed)

# First package creates a directory symlink:
C:\Users\USER\AppData\Local -> C:\Users\USER\Dotfiles\package1\AppData\Local

# Second package needs different AppData\Local contents:
# winstow automatically unfolds:
C:\Users\USER\AppData\Local\
  ├── app1 -> C:\Users\USER\Dotfiles\package1\AppData\Local\app1
  └── app2 -> C:\Users\USER\Dotfiles\package2\AppData\Local\app2

Best Practices

  1. Always use -n first to preview changes before applying them
  2. Use -v for debugging to see detailed operations and understand what's happening
  3. Set up .winstowrc for common settings to avoid repeating command-line options
  4. Keep packages focused - one purpose per package (e.g., separate vim, git, bash packages)
  5. Test in a temp directory before applying to your real dotfiles
  6. Use version control (git) in your stow directory to track changes
  7. Document your packages with README files explaining what each package contains
  8. Backup before --override - this option is destructive and removes existing files!
  9. Backup before --adopt - this option is destructive and removes existing files!

Troubleshooting

"Permission denied" Error

Problem: Cannot create symbolic links

Solutions:

  1. Enable Developer Mode (Settings → For developers)
  2. Run as Administrator
  3. Check Windows version (requires Windows Vista+)

Symlinks Not Working

Problem: Symlinks appear as files or don't work

Check:

  • Developer Mode is enabled OR running as Administrator
  • Using proper Windows symlink support (not shortcuts)
  • Target files/directories exist

"Conflict" Error

Problem: Conflict: path already exists

Solutions:

# Option 1: Adopt existing file
winstow --adopt mypackage

# Option 2: Override existing file
winstow --override mypackage

# Option 3: Manually remove/backup the file
move conflicting-file conflicting-file.backup
winstow mypackage

Dry-Run Shows Different Results

Problem: Dry-run output doesn't match actual execution

Note: Dry-run simulates operations but doesn't account for all dynamic conditions. Always make sure you have a backup of your files.

Development

Building

# Development build
cargo build

# Release build (optimized)
cargo build --release

# Run tests
cargo test

# Run tests including ignored ones (requires Developer Mode)
cargo test -- --ignored

Project Structure

winstow/
├── src/
│   ├── main.rs          # Entry point and CLI routing
│   ├── cli.rs           # Command-line argument parsing
│   ├── config.rs        # Configuration file handling
│   ├── error.rs         # Error types
│   ├── logger.rs        # Logging infrastructure
│   ├── path_utils.rs    # Path manipulation utilities
│   ├── fs_ops.rs        # Windows filesystem operations
│   ├── planner.rs       # Action planning and execution
│   ├── stow.rs          # Stow operation logic
│   ├── unstow.rs        # Unstow operation logic
│   ├── adopt.rs         # Adopt/override functionality
│   └── ignore.rs        # Pattern matching
├── tests/
│   └── integration_tests.rs  # Integration tests
├── docs/
│   └── TECHNICAL_CONCEPT.md  # Technical documentation
├── Cargo.toml
├── README.md
├── LICENSE
├── CHANGELOG.md
└── DISTRIBUTION.md

Testing

All tests use temporary directories and should be safe to run:

# All tests (unit + integration)
cargo test

# Just integration tests
cargo test --test integration_tests

# Test with output
cargo test -- --nocapture

# Tests requiring symlink creation (needs Developer Mode)
cargo test -- --ignored

Contributing

Contributions are welcome! Please:

  1. Fork the repository
  2. Create a feature branch
  3. Add tests for new functionality
  4. Ensure all tests pass
  5. Submit a pull request

License

MIT License - see LICENSE file for details.

Acknowledgments

  • Inspired by GNU Stow by Bob Glickstein
  • Built with Rust
  • Uses clap for CLI parsing
  • Uses windows-rs for Windows API access
  • Uses thiserror for error handling
  • Uses serde for serialization/deserialization
  • Uses toml for configuration file parsing
  • Uses glob for pattern matching
  • Uses dirs for platform-specific directory paths

Support

About

Windows-native symlink farm manager inspired by GNU Stow

Topics

Resources

License

Stars

Watchers

Forks

Languages