Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
name: CI

on:
push:
pull_request:
workflow_dispatch:

permissions:
contents: read
Expand Down Expand Up @@ -50,6 +48,12 @@ jobs:
run: cargo check --locked --all-targets
- name: Clippy (warnings are errors)
run: cargo clippy --locked --all-targets -- -D warnings
- name: Install shells for completion tests (Linux)
if: runner.os == 'Linux'
run: sudo apt-get update && sudo apt-get install -y zsh fish
- name: Install Fish for completion tests (macOS)
if: runner.os == 'macOS'
run: command -v fish >/dev/null || brew install fish
- name: Unit and integration tests
run: cargo test --locked --all-targets
- name: Documentation tests
Expand Down
59 changes: 15 additions & 44 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ one executable for macOS and Linux. Read README.md before changing its behavior.
does not require a separate Rust or Git installation.
- Run `cargo fmt --all`, `cargo clippy --locked --all-targets -- -D warnings`,
`cargo test --locked --all-targets`, and `cargo test --locked --doc`.
- Completion integration tests require Bash, Zsh, and Fish on PATH.
- Run `taplo fmt` and `taplo fmt --check` with taplo-cli 0.10.0.
- Every Rust import must be its own `use` statement. Do not use grouped braces.
rustfmt's `imports_granularity = "Item"` enforces this on the pinned nightly.
Expand All @@ -25,50 +26,20 @@ one executable for macOS and Linux. Read README.md before changing its behavior.

## Architecture and invariants

- config.rs owns editable TOML mappings, validation, atomic config writes, and the shared
operation lock. Repository path, init subdirectory, and entry target are
separate concepts: destination = repository / subdir / target / relative file.
Without `add --to`, sources inside Home use their Home-relative path; sources
outside Home use their absolute path with the leading `/` removed. The same
default applies to list imports. Explicit targets override either default.
- Application data defaults to `$HOME/.filetrail` on both macOS and Linux. The
`--data-dir` option overrides this location for all configuration, mappings,
synchronization state, locks, sockets, and logs. Use the same resolved data
directory when spawning the daemon or rendering system service definitions.
- sync.rs reconciles current filesystem contents, records ownership and content
baselines, protects external destination edits, and copies without following
symlinks. Events are hints; periodic scans recover missed events.
- state.rs persists synchronization state in `state.db` using bundled SQLite.
Ownership, baselines, conflicts, and the last sync timestamp live in separate
tables. Apply related changes in one transaction, updating only changed rows.
Ownership survives a source deletion so Git can still commit that deletion.
Conflicts may refer to files not yet owned by FileTrail.
Validate application_id and user_version before accessing a database; never
silently reset damaged or unknown schemas. Publish a new database only after
its initial transaction succeeds. Reads and dry runs must not create a database.
There is no legacy JSON state reader or migration path. Callers hold the shared
operation lock across a state read/modify/write sequence; SQLite also provides
transactional consistency for state readers and writers.
- manifest.rs parses file-list lines as `source [target]` separated by whitespace.
Single/double quotes and escapes support spaces in either path; comments and
blank lines are allowed. Never execute a shell or expand variables in the list.
Reject extra fields, empty paths, and malformed quotes with a line-numbered
error, and validate the complete list before saving any mappings.
- git.rs handles local status/diff/commit. Background sync never stages or commits.
A commit includes only previously synchronized, still-managed paths. Preexisting
staged changes cause a refusal, without changing the index.
- daemon.rs owns native watching, periodic reconciliation, and the local socket.
All disk mutations share operation.lock; daemon.lock prevents duplicate daemons.
CLI config edits are atomic and picked up by the daemon without restarting it.
- service.rs renders/installs user-level launchd or systemd definitions.
- Default synchronization preserves deleted source files in the destination.
Opt-in deletion applies only to previously synchronized paths. A missing source
root directory must never trigger mass deletion.
- Never permit repository-relative paths to escape via `..`, `.git`, or a
destination ancestor symlink. Do not overwrite external destination edits
unless the user explicitly requests conflict resolution for that path.
- Default commit messages start with `filetrail: ` and list every selected file
change. Explicit user messages are preserved. No automatic commit or push.
Core modules: `config.rs` manages mappings, `state.rs` persists sync state,
`sync.rs` and `daemon.rs` handle synchronization, and `git.rs` handles Git operations.
Consult the source and tests for implementation details.

- CLI, daemon, and services share the same data directory and serialize mutations.
- Keep sync destinations inside the configured repository. Never follow symlinks
outside it or modify `.git` through synchronization.
- Protect external destination edits. Deletion is opt-in and limited to managed
files; an unavailable source directory must never trigger mass deletion.
- Background sync never stages or commits. Explicit commits include only managed
changes and must preserve the user's existing staging.
- Validate before writing, update state atomically, and keep dry runs read-only.
Never silently discard corrupt state.
- Shell setup must preserve existing user configuration and safely quote paths.

## Using Filetrail as an agent

Expand Down
50 changes: 48 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,53 @@ cargo install --path . --locked
filetrail --help
```

To install FileTrail and enable Tab completion in one step:

```sh
./install.sh
```

The script detects Bash, Zsh, or Fish from `$SHELL`. You can select one explicitly
with `./install.sh zsh`. It installs with Cargo, then configures that shell's
completion. Open a new shell afterward. The installation root defaults to
`${CARGO_HOME:-$HOME/.cargo}`; set `CARGO_INSTALL_ROOT` to override it.

## Tab completion

If you installed FileTrail with `cargo install`, enable completion with:

```sh
filetrail completions --install
```

This detects your shell from `$SHELL`. To select a shell explicitly:

```sh
filetrail completions zsh --install
filetrail completions bash --install
filetrail completions fish --install
```

Run the command for the shell you use, then open a new shell. Tab completes
subcommands (including `daemon` and `service` actions), options, and file paths.
For example, try `filetrail da<Tab>`, `filetrail daemon st<Tab>`, or
`filetrail add --f<Tab>`.

Installation preserves existing shell configuration and is safe to repeat. It
uses `.zshrc` (respecting `ZDOTDIR`), `.bashrc` and Bash's active login profile,
or Fish's completion directory (respecting `XDG_CONFIG_HOME`). Home paths use
`$HOME` in the installed hooks and command output, so your username is not embedded.
Paths outside Home retain their absolute location. Completion stays
in sync when you upgrade the executable at the same location. Run installation
again if you move it. To remove completion, delete the marked FileTrail block
from the configured files printed by the install command.

To print a completion script for manual setup, omit `--install`:

```sh
filetrail completions zsh
```

## Get started

```sh
Expand Down Expand Up @@ -123,7 +170,7 @@ already staged. Set your Git name and email before your first commit.
Without `-m`, FileTrail generates a message listing the selected changes:

```text
filetrail: sync 3 files (+1 ~1 -1)
FileTrail: sync 3 files (+1 ~1 -1)

add "macos/.config/nvim/init.lua"
delete "macos/.oldrc"
Expand Down Expand Up @@ -198,7 +245,6 @@ filetrail doctor
filetrail logs --follow
filetrail --help
filetrail add --help
filetrail completions zsh
```

For development instructions, see [AGENTS.md](AGENTS.md).
46 changes: 44 additions & 2 deletions README_zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,49 @@ cargo install --path . --locked
filetrail --help
```

如需一次完成 FileTrail 安装和 Tab 补全配置:

```sh
./install.sh
```

脚本根据 `$SHELL` 识别 Bash、Zsh 或 Fish,也可以用 `./install.sh zsh` 显式指定。
它先通过 Cargo 安装,再配置所选 shell 的补全,完成后重新打开 shell 即可。
安装根目录默认为 `${CARGO_HOME:-$HOME/.cargo}`,可通过 `CARGO_INSTALL_ROOT` 覆盖。

## Tab 补全

如果使用 `cargo install` 安装 FileTrail,执行以下命令启用补全:

```sh
filetrail completions --install
```

该命令根据 `$SHELL` 识别 shell,也可以显式指定:

```sh
filetrail completions zsh --install
filetrail completions bash --install
filetrail completions fish --install
```

执行你所用 shell 对应的命令,然后重新打开 shell。Tab 可以补全子命令
(包括 `daemon` 和 `service` 的操作)、选项及文件路径。例如:
`filetrail da<Tab>`、`filetrail daemon st<Tab>`、`filetrail add --f<Tab>`。

安装会保留已有 shell 配置,重复执行不会添加重复配置。配置位置为 `.zshrc`
(遵循 `ZDOTDIR`)、`.bashrc` 和 Bash 当前使用的登录配置文件,或 Fish 的补全目录
(遵循 `XDG_CONFIG_HOME`)。安装的补全配置和命令输出使用 `$HOME` 表示 Home 路径,
不写入用户名;Home 以外的路径保留绝对位置。在相同位置升级可执行文件后,补全会同步更新;
移动可执行文件后需重新安装补全。若要移除补全,删除安装命令所列配置文件中
带有 FileTrail 标记的配置块即可。

如需手动配置,可省略 `--install`,只输出补全脚本:

```sh
filetrail completions zsh
```

## 开始使用

```sh
Expand Down Expand Up @@ -113,7 +156,7 @@ filetrail commit -- macos/.zshrc
不传 `-m` 时,FileTrail 会自动生成列出本次变化的消息:

```text
filetrail: sync 3 files (+1 ~1 -1)
FileTrail: sync 3 files (+1 ~1 -1)

add "macos/.config/nvim/init.lua"
delete "macos/.oldrc"
Expand Down Expand Up @@ -184,7 +227,6 @@ filetrail doctor
filetrail logs --follow
filetrail --help
filetrail add --help
filetrail completions zsh
```

开发说明见 [AGENTS.md](AGENTS.md)。
30 changes: 30 additions & 0 deletions install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#!/bin/sh
set -eu

usage() {
echo 'Usage: ./install.sh [bash|zsh|fish]'
echo 'Install FileTrail with Cargo and configure Tab completion (defaults to $SHELL).'
echo 'CARGO_INSTALL_ROOT overrides the installation root; otherwise CARGO_HOME or ~/.cargo is used.'
}

if [ "$#" -gt 1 ]; then
usage >&2
exit 2
fi
case "${1-}" in
-h|--help) usage; exit 0 ;;
esac
filetrail_shell=${SHELL-}
filetrail_shell=${1:-${filetrail_shell##*/}}
case "$filetrail_shell" in
bash|zsh|fish) ;;
*) echo 'Specify bash, zsh, or fish: ./install.sh zsh' >&2; exit 2 ;;
esac

filetrail_project=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
filetrail_install_root=${CARGO_INSTALL_ROOT:-${CARGO_HOME:-"$HOME/.cargo"}}
mkdir -p "$filetrail_install_root"
filetrail_install_root=$(CDPATH= cd -- "$filetrail_install_root" && pwd)
cd "$filetrail_project"
cargo install --path . --locked --root "$filetrail_install_root"
"$filetrail_install_root/bin/filetrail" completions "$filetrail_shell" --install
Loading
Loading