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:
fuse3runtime (sudo apt install fuse3or equivalent). No development headers needed to run. - macOS: macFUSE
Subcommands
| Subcommand | What it does |
|---|---|
mount SOURCE [MOUNTPOINT] | Mount (omit mountpoint for auto temp dir + print path). Use --daemon to run in background. |
status | List active daemon mounts (PID, source, mountpoint, uptime) |
stop --pid <PID> / stop <MOUNTPOINT> | Unmount a running daemon mount by PID or mountpoint |
open SOURCE | Mount + open in file manager |
cp SOURCE DEST | Mount, cp -a the filtered view to DEST, unmount |
list SOURCE | Preview the filtered view without mounting (flat listing, no summary) |
exec SOURCE -- <command> | Mount, run any command against the filtered view, unmount |
tar SOURCE OUTPUT | Mount, create tarball of the filtered view, unmount (compression from suffix) |
zip SOURCE OUTPUT | Mount, 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
| Flag | Description |
|---|---|
--tree / -t | Show full recursive directory tree |
--summary / -s | Show 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:
| Suffix | Compression |
|---|---|
.tar | none |
.tar.gz / .tgz | gzip |
.tar.xz / .txz | xz |
.tar.bz2 / .tbz2 / .tbz | bzip2 |
.tar.zst / .tzst | zstd |
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
| Method | Command |
|---|---|
| Foreground process | Press Ctrl+C |
| Daemon mount | clean-mount stop --pid <PID> |
| Daemon mount | clean-mount stop /tmp/mirror |
| Manual (Linux) | fusermount3 -u /tmp/mirror |
| Manual (macOS) | umount /tmp/mirror |
| Force unmount | fusermount3 -uz /tmp/mirror |
Logging
RUST_LOG=info clean-mount mount /source /mnt
RUST_LOG=clean_mount=debug clean-mount cp /source /dest
Common Options
| Flag | Description |
|---|---|
--allow-other | Allow other users to access the mount |
--allow-root | Allow root to access the mount |
--default-permissions | Let kernel enforce permission checks |
--ttl-secs <SECONDS> | Entry and attribute TTL (default: 1) |
--hide-git | Always hide .git files/directories |
--hide-gitignore | Always hide .gitignore files |
--ignore-file <NAME> | Ignore file to use instead of .gitignore (default: .gitignore); errors if not found |
--no-ignore | Disable 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 |
--clipboard | Copy the auto temp mount path to clipboard |
Note: Ignore rules are loaded at startup. If rules change, remount to reload them. A missing default
.gitignoreproduces a warning; a missing explicitly requested--ignore-fileis 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 /mntThe 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:
- Fork the repository
- Create a feature branch (
git checkout -b feature/my-feature) - Make your changes
- Run the tests (
cargo test && cargo build --release) - Submit a pull request
Please keep changes focused and include tests when adding new functionality.
License
This project is licensed under the MIT License.