![Discord](https://img.shields.io/discord/1381424110831145070?style=flat-square&logo=rust&link=https%3A%2F%2Fdiscord.gg%2FfHmRmuBDxF)
[![Crates.io](https://img.shields.io/crates/v/clonetree)](https://crates.io/crates/clonetree)
[![docs.rs](https://img.shields.io/docsrs/clonetree)](https://docs.rs/clonetree)

# clonetree

**A copy‑on‑write directory library for Rust**

`clonetree` is a **Rust crate** for fast directory duplication. It uses
filesystem‑level *reflinks* so large trees are cloned quickly and consume no
extra space until files diverge.

---
## Community

Want to contribute? Have ideas or feature requests? Come tell us about it on
[Discord](https://discord.gg/fHmRmuBDxF).

---

## Highlights

* **Fast copy‑on‑write clone** on APFS, Btrfs, XFS, bcachefs, overlayfs, ReFS…

* **Powered by [`reflink-copy`](https://github.com/cargo-bins/reflink-copy)** for portable block‑cloning.

* **Flexible glob patterns** to include/exclude files (uses `!` prefix for exclusions).

* **Graceful fallback** to `std::fs::copy` when reflinks are unsupported.

* **Symlink preservation** — symbolic links are recreated with their original targets.

* **Empty directory preservation** — empty directories are included in the clone.

* **Pure Rust**, no unsafe code, minimal deps.

---

## Library quick start

Clone only Rust source files under `src/` into `./sandbox`, while excluding
unit tests. This demonstrates the precedence rules from the
[`ignore`](https://docs.rs/ignore) crate: later patterns override earlier ones.

```rust
use clonetree::{clone_tree, Options};

fn main() -> anyhow::Result<()> {
    // Include all source files but drop tests
    let opts = Options::new()
        .glob("src/**")         // positive include
        .glob("!src/tests/**"); // negative exclude; overrides the line above

    clone_tree("./", "./sandbox", &opts)?;
    Ok(())
}
```

---

## Glob syntax

`clonetree` accepts the same glob rules as a single line in a `.gitignore`
file. Prefix a pattern with `!` to **exclude** matching paths instead of
including them.

| Pattern             | Description                                      |
| ------------------- | ------------------------------------------------ |
| `*.rs`              | All Rust source files in the current directory   |
| `**/*.toml`         | Any `.toml` file at any depth                    |
| `!target/**`        | **Exclude** Cargo build artefacts                |
| `images/**/thumb_*` | Every `thumb_*` file under `images/` recursively |

Rules:

* `*` matches any sequence of characters except path separators.
* `**` matches across directory boundaries.
* `?` matches exactly one character.
* A trailing `/` restricts the pattern to directories only.
* Patterns are evaluated **in the order they are given**; later patterns can
  override earlier ones (precedence follows the `ignore` crate).

---

## `ctree` ‑ command‑line tool

The crate ships with a convenience binary so users can benefit without writing code.

### Install

```bash
cargo install ctree
```

### Semantics

`ctree` copies a directory to a specified destination. The source must be a
directory, and the destination must not exist. 

### Basic usage

```bash
ctree <SRC> <DEST> [OPTIONS]

OPTIONS:
  -g, --glob <GLOB>      Match or exclude glob (repeatable)
      --strategy <auto|single-call|full-traversal>
                         Cloning strategy (defaults to auto)
  -q, --quiet            Suppress progress output
  -h, --help             Show this help
```

Example: snapshot a repo while excluding Git metadata and build output:

```bash
ctree . ./sandbox \
  --glob '!target/**' \
  --glob '!.git/**'
```

### Strategies

`clonetree` offers three strategies:

* `auto` (default): on macOS/APFS it issues a **single `clonefile` call** on the
  root directory (fastest, atomic, COW). Else it falls back to traversal.
* `single-call`: force the one-shot clone (macOS only; destination must not
  exist; incompatible with glob filters).
* `full-traversal`: user-space walk that reflinks each file individually (works
  everywhere and honors glob filters).

---

## Symlink handling

Symbolic links are preserved as symbolic links — they are not followed or
dereferenced. The link target is copied verbatim, so relative symlinks maintain
their relative paths in the cloned tree.

| Strategy         | Symlink behaviour                                          |
| ---------------- | ---------------------------------------------------------- |
| `single-call`    | Preserved by the kernel's `clonefile(2)` (macOS only)      |
| `full-traversal` | Recreated via `symlink(2)` (Unix) or `CreateSymbolicLink` (Windows) |

---

## Filesystem support matrix

Via [reflink-copy](https://crates.io/crates/reflink-copy)

| OS / FS                        | Reflink supported | API used                          | Behaviour          |
| ------------------------------ | ----------------- | --------------------------------- | ------------------ |
| macOS 10.13+ / APFS            | ✅                 | `clonefile(2)`                    | COW clone          |
| iOS / APFS                     | ✅                 | `clonefile(2)`                    | COW clone          |
| Linux 6.7+ / Btrfs             | ✅                 | `FICLONE` ioctl                   | COW clone          |
| Linux 5.4+ / XFS (`reflink=1`) | ✅                 | `FICLONE` ioctl                   | COW clone          |
| Linux 6.1+ / bcachefs          | ✅                 | `remap_file_range`                | COW clone          |
| Linux 5.13+ / overlayfs        | ✅                 | `remap_file_range`                | COW clone          |
| Windows Server 2016+ / ReFS    | ✅                 | `FSCTL_DUPLICATE_EXTENTS_TO_FILE` | COW clone          |
| ext4 (Ubuntu/Fedora default)   | ❌                 | –                                 | Byte‑for‑byte copy |
