Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

clean-mount

A read-only FUSE filesystem that mirrors a directory while hiding files matched by .gitignore.

Make ignored files appear nonexistent to ls, find, zip, tar, rsync, editors, and AI agents.


clean-mount mounts a read-only FUSE filesystem over any directory. Files and directories matched by .gitignore rules are invisible — they return ENOENT as if they never existed. Nested .gitignore files are respected.

Installation

From crates.io

cargo install clean-mount

From git

git clone <repo-url>
cd clean-mount
cargo build --release
./target/release/clean-mount --help

From source (local install)

After cloning, install the binary to ~/.cargo/bin/:

cd clean-mount
cargo install --path .
clean-mount --help

Docker

docker build -t clean-mount .

Requirements

  • Linux: fuse3 runtime (sudo apt install fuse3 or equivalent). No development headers needed to run.
  • macOS: macFUSE

Subcommands

SubcommandWhat it does
mount SOURCE [MOUNTPOINT]Mount (omit mountpoint for auto temp dir + print path). Use --daemon to run in background.
statusList active daemon mounts (PID, source, mountpoint, uptime)
stop --pid <PID> / stop <MOUNTPOINT>Unmount a running daemon mount by PID or mountpoint
open SOURCEMount + open in file manager
cp SOURCE DESTMount, cp -a the filtered view to DEST, unmount
list SOURCEPreview the filtered view without mounting (flat listing, no summary)
exec SOURCE -- <command>Mount, run any command against the filtered view, unmount
tar SOURCE OUTPUTMount, create tarball of the filtered view, unmount (compression from suffix)
zip SOURCE OUTPUTMount, create .zip of the filtered view, unmount
complete [SHELL]Generate shell completion script (bash, zsh, fish, elvish, powershell). Use --install to auto-add the eval line to your shell rc file.

tar, zip, cp, mount, open, exec, and list accept the same common options (--hide-git, --ignore-file, etc.). complete does not need them.

mount

# Auto temp dir: prints mount path, Ctrl+C to unmount
clean-mount mount /path/to/project

# Or mount at a specific directory
mkdir -p /tmp/mirror
clean-mount mount /path/to/project /tmp/mirror

Use another terminal to inspect the filtered view:

ls -la /tmp/mirror
cat /tmp/mirror/src/main.rs
cd /tmp/mirror && zip -r ~/filtered.zip .

Daemon mode

Run the mount as a background daemon (requires an explicit mountpoint):

clean-mount mount /path/to/project /tmp/mirror --daemon
# Returns immediately, prints PID

The process forks and detaches from the terminal. The mount stays alive until:

  • The daemon is stopped with clean-mount stop --pid <PID>
  • The mountpoint is unmounted manually (fusermount3 -u /tmp/mirror)
  • The system is rebooted

Use clean-mount status to see all active daemon mounts.

status

clean-mount status

Lists all active daemon mounts. Dead PIDs are filtered out automatically.

$ clean-mount status
  PID  SOURCE                    MOUNTPOINT                               UPTIME
12345 /home/user/project       /tmp/mirror                               2h 15m

Each entry corresponds to a mount started with mount --daemon. The registry is stored at $XDG_RUNTIME_DIR/clean-mount/mounts/<pid>.mount.

stop

# By PID
clean-mount stop --pid 12345

# By mountpoint
clean-mount stop /tmp/mirror

Unmounts a running daemon mount. Internally runs fusermount3 -u (or umount as fallback) against the resolved mountpoint. The daemon process detects the unmount and exits cleanly.

Use clean-mount status to find the PID of a running daemon mount.

cp

# Copy project without node_modules, .venv, build artifacts
clean-mount cp /path/to/node-project /tmp/node-source-only

Internally this does: mount → cp -a → unmount. Your single command.

list — Preview the filtered view (dry-run)

clean-mount list /path/to/project [--tree] [--summary]

Shows what the filtered view would contain without mounting anything. Useful for debugging ignore rules before running cp, tar, or rsync.

By default, list shows a flat top-level listing (like ls) with no summary. Use --tree for the full recursive tree, and --summary for file/ignored/size statistics.

Examples

# Flat top-level listing (default, fast)
$ clean-mount list ~/my-node-project
src/
node_modules/
package.json
# Full recursive tree with summary stats
$ clean-mount list ~/my-node-project --tree --summary
src/
  index.js
  lib/
    utils.js
    parser.js
package.json
12 files (847 ignored, 512.7 MB total)
# Show everything, ignoring any ignore rules
clean-mount list ~/my-node-project --no-ignore

# Hide extra paths on top of the ignore file (overrides it)
clean-mount list ~/my-node-project --exclude '*.min.js' --exclude build/

# Keep a gitignored file visible
clean-mount list ~/my-node-project --include keep.env

# Ad-hoc filtering without any ignore file
clean-mount list ~/my-node-project --no-ignore --exclude '*.log' --exclude .venv

Options

FlagDescription
--tree / -tShow full recursive directory tree
--summary / -sShow file/ignored/size summary

All common options are also supported. --exclude/--include take gitignore-style patterns (repeatable) and override the ignore file; pair --no-ignore with --exclude for ad-hoc filtering without any ignore file.

tar

Supported suffixes:

SuffixCompression
.tarnone
.tar.gz / .tgzgzip
.tar.xz / .txzxz
.tar.bz2 / .tbz2 / .tbzbzip2
.tar.zst / .tzstzstd
clean-mount tar /path/to/project /tmp/project.tgz
clean-mount tar /path/to/project /tmp/project.tar.xz

Internally: mount → tar -acf → unmount.

zip

clean-mount zip /path/to/project /tmp/project.zip

Creates a zip archive of the filtered view. Internally: mount → zip -r → unmount.

complete

# Add to ~/.bashrc, ~/.zshrc, ~/.config/fish/config.fish, etc.
eval "$(clean-mount complete)"

Generates a shell completion script so you can tab-complete subcommands, options, and paths.

Auto-install

clean-mount complete --install

Detects your shell from $SHELL and appends eval "$(clean-mount complete)" to the appropriate rc file (.bashrc, .zshrc, .config/fish/config.fish, .config/elvish/rc.elv). Pass a shell explicitly to install for a different shell:

clean-mount complete --install zsh

Manual install

Auto-detects your shell from $SHELL. Pass the shell name explicitly for other shells:

# bash
clean-mount complete bash > ~/.local/share/bash-completion/completions/clean-mount

# zsh (ensure ~/.zsh/completions is in your fpath)
mkdir -p ~/.zsh/completions
clean-mount complete zsh > ~/.zsh/completions/_clean-mount

# fish
clean-mount complete fish > ~/.config/fish/completions/clean-mount.fish

exec

For quick archive creation, consider the tar and zip subcommands instead.

# rsync
clean-mount exec /path/to/project -- rsync -avz . user@server:/deploy-path

# cp to a non-default location with extra flags
clean-mount exec /path/to/project -- cp -r . /tmp/my-copy

Quick peek at what would be copied:

clean-mount exec /path/to/project -- ls -la

The command runs with the filtered view as its working directory — use . for “everything here”. Use {MOUNT} in arguments only when you need the absolute path explicitly:

clean-mount exec /path/to/project -- cp -r {MOUNT}/src /tmp/src-only

open

clean-mount open /path/to/project

Opens a temporary mount in your system file manager (nautilus, dolphin, finder, etc.). Press Ctrl+C to unmount and close.

Use Cases

📦 Copy a Node.js project without node_modules

clean-mount cp /path/to/node-project /tmp/node-source-only

Since node_modules is typically in .gitignore, it simply won’t exist in the mounted view.

🐍 Archive a Python project without venv / __pycache__

clean-mount tar /path/to/python-project /tmp/project-source.tar.gz

Virtual environments, cache directories, and other gitignored files disappear automatically.

🚀 Rsync only source files to a server

clean-mount exec /path/to/project -- rsync -avz . user@server:/deploy-path

Build artifacts, dependencies, and configs (if gitignored) are excluded from the transfer.

🤖 Feed a clean project tree to an AI coding agent

clean-mount mount /path/to/project /tmp/clean --hide-git --hide-gitignore
# Point your AI agent at /tmp/clean

The agent only sees what matters — your actual source code.

Node.js

clean-mount cp /path/to/node-project /tmp/node-source-only

Since node_modules is typically in .gitignore, it simply won’t exist in the mounted view.

Python

clean-mount tar /path/to/python-project /tmp/project-source.tar.gz

Virtual environments, cache directories, and other gitignored files disappear automatically.

Rsync

clean-mount exec /path/to/project -- rsync -avz . user@server:/deploy-path

Build artifacts, dependencies, and configs (if gitignored) are excluded from the transfer.

AI Agents

clean-mount mount /path/to/project /tmp/clean --hide-git --hide-gitignore
# Point your AI agent at /tmp/clean

The agent only sees what matters — your actual source code.

Usage

Usage

Mount

# Auto temp dir: prints mount path, Ctrl+C to unmount
clean-mount mount /path/to/project

# Or mount at a specific directory
mkdir -p /tmp/mirror
clean-mount mount /path/to/project /tmp/mirror

Use another terminal to inspect the filtered view:

ls -la /tmp/mirror
cat /tmp/mirror/src/main.rs
cd /tmp/mirror && zip -r ~/filtered.zip .

Unmount

MethodCommand
Foreground processPress Ctrl+C
Daemon mountclean-mount stop --pid <PID>
Daemon mountclean-mount stop /tmp/mirror
Manual (Linux)fusermount3 -u /tmp/mirror
Manual (macOS)umount /tmp/mirror
Force unmountfusermount3 -uz /tmp/mirror

Logging

RUST_LOG=info clean-mount mount /source /mnt
RUST_LOG=clean_mount=debug clean-mount cp /source /dest

Common Options

FlagDescription
--allow-otherAllow other users to access the mount
--allow-rootAllow root to access the mount
--default-permissionsLet kernel enforce permission checks
--ttl-secs <SECONDS>Entry and attribute TTL (default: 1)
--hide-gitAlways hide .git files/directories
--hide-gitignoreAlways hide .gitignore files
--ignore-file <NAME>Ignore file to use instead of .gitignore (default: .gitignore); errors if not found
--no-ignoreDisable ignore-file processing entirely (show all files); pair with --exclude to filter ad-hoc without an ignore file
--exclude <PATTERN>Extra gitignore-style pattern(s) to hide; overrides the ignore file and --include. Repeatable
--include <PATTERN>Gitignore-style pattern(s) to keep visible even if the ignore file hides them; overridden by --exclude. Repeatable
--clipboardCopy the auto temp mount path to clipboard

Note: Ignore rules are loaded at startup. If rules change, remount to reload them. A missing default .gitignore produces a warning; a missing explicitly requested --ignore-file is an error.

--exclude and --include take gitignore-style glob patterns and may be repeated. Precedence (highest to lowest): --hide-git/--hide-gitignore, --exclude, --include, then the ignore file. --no-ignore disables only the ignore-file rules, so it pairs naturally with --exclude (or --include) for one-off filtering when no .gitignore exists.

Note that --include follows git semantics: including a file inside an ignored directory has no effect unless the directory itself (and any other ignored ancestors) is also included. To keep secret/keep.env visible when .gitignore hides secret/, pass both --include secret/ --include secret/keep.env (or --include secret/ alone, which reopens the whole directory).

# Ad-hoc filtering without any ignore file
clean-mount list /path/to/project --no-ignore --exclude '*.log' --exclude .venv

# Hide extra paths on top of the ignore file
clean-mount cp /path/to/project /tmp/copy --exclude build/

# Keep a gitignored file visible
clean-mount tar /path/to/project /tmp/project.tar.gz --include keep.env

Docker

Build the image:

docker build -t clean-mount .

Run:

docker run --rm -it \
  --device /dev/fuse \
  --cap-add SYS_ADMIN \
  --security-opt apparmor=unconfined \
  -v "$PWD/project:/source:ro" \
  clean-mount \
  mount /source /mnt

Limitation: FUSE mounts are per-mount-namespace — they happen inside the container and are not visible from the host. To inspect the filtered view from another terminal:

docker exec -it <container-id> ls /mnt

The primary use of the Docker image is building and testing in CI/CD pipelines.

Development

Prerequisites (for building from source)

Building requires the FUSE3 development headers to compile the fuser crate:

# Debian / Ubuntu
sudo apt install libfuse3-dev pkg-config

# Fedora
sudo dnf install fuse3-devel

# macOS
brew install macfuse

Pre-commit hooks (prek)

This repo uses prek to run cargo fmt and cargo clippy before each commit.

# Install prek (one time)
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/j178/prek/releases/download/v0.4.11/prek-installer.sh | sh

# Install the git hook (per clone)
prek install

Running tests

cargo test

Building for release

cargo build --release

Contributing

Contributions are welcome! Here’s how to get started:

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/my-feature)
  3. Make your changes
  4. Run the tests (cargo test && cargo build --release)
  5. Submit a pull request

Please keep changes focused and include tests when adding new functionality.

License

This project is licensed under the MIT License.