Community研究与数据分析github.com

dragon-fish/config-file-versioning

Auto-version local config files via git + OS file watchers. A drop-in skill for AI coding agents (Claude Code, Codex, Gemini CLI, etc.).

兼容平台Claude CodeCodex CLI~CursorGemini CLI
npx add-skill dragon-fish/config-file-versioning

name: config-file-versioning description: 给小型本地配置文件设置自动版本备份,便于回滚被覆写/误改。当用户希望给某几个 config 文件(如 /.ssh/config、/.zshrc、应用 dotfile 等)加版本史/防覆写/可一键回滚时使用。统一架构:separate-git-dir + 白名单 .gitignore + 操作系统级文件监听 + 自动 commit 脚本。一个被保护的 config 域 = 一个独立小仓库。原生支持 macOS(launchd),Linux/其他可参照同思路换用 systemd path unit / inotifywait / gitwatch 实现监听层。 license: MIT

Skill: Config File Versioning

给"小型 + 重要 + 不希望被悄悄覆盖"的本地配置文件做自动版本备份。

每次被监听文件实际内容变化时,10 秒内自动产生一条 git commit,保留在一个本地 separate-git-dir 仓库里。一键回滚到任意历史版本。


何时使用

用户出现下列任一触发信号就用本 skill:

  • "给 <某文件> 加自动版本/备份/防覆写"
  • "我不想哪天某程序又把我的 <某 dotfile> 覆盖了"
  • "自动 commit 当 <某 config> 有变化"
  • "参考 <已有备份目录> 给我备份 <其他文件>"
  • 用户提到关键 config 被某工具/手贱覆盖丢失,问怎么以后预防

不适用于:

  • 大型代码仓库(直接用普通 git workflow 就行)
  • 跨机器同步(本 skill 只做本地版本史;要同步加 syncthing 或手动 git push 到私有 remote)
  • 频繁高速变动的文件(.bash_historyhistory.jsonl 之类——会产生海量 commit)

架构总览

   <被保护文件>           (实际位置,例 ~/.ssh/config)
        │ 修改
        ▼
   OS 级文件监听
   ┌──────────────────────────────┬──────────────────────┬─────────┐
   │ macOS: launchd WatchPaths    │ Linux: systemd .path │ 其他    │
   └──────────────────────────────┴──────────────────────┴─────────┘
        │ 触发(10s 节流后)
        ▼
   auto-commit.sh
        │ git add -A → 仅当 staged diff 非空时 commit
        ▼
   <BACKUP_ROOT>/<domain>-config.git/   ← 真正的 git repo(separate-git-dir)
        ▲
        │ 由 worktree 根的 ".git"(一行 gitdir: 文本)连接

关键设计点:

设计理由
每个 config 域独立仓库(不合并到一个大仓库)历史干净;可独立启用/禁用/迁移
separate-git-dirworktree 整个被某程序删光时,历史依然在 <BACKUP_ROOT> 里活着
白名单 .gitignore* + !<被保护文件>不会误吞同目录下的运行时数据/缓存/临时文件
事件驱动监听 + 节流(默认 10s)平时无常驻进程;避免一次写入产生多条 commit
commit 脚本里 git diff --cached --quiet 检查内容真无变化(仅 mtime 改)时静默退出

实施步骤

步骤 0:跟用户对齐 4 个变量

变量含义推荐默认值
<DOMAIN>配置域简称(仅小写字母数字短横线,作目录名/Label 后缀)跟用户讨论;例:ssh / zsh / vscode / claude
<WORKTREE>被保护文件所在的目录绝对路径(git worktree 根)通常等于被监听文件所在目录
<FILES>要监听的绝对路径列表(≥ 1)由用户指定
<BACKUP_ROOT>仓库 + 脚本 + 服务模板的存放根目录必问:默认建议 ~/.local/share/config-versioning/<DOMAIN>/(XDG 数据目录约定);若用户已有专属备份/同步目录(如 ~/Documents/backups/~/Sync/...、自己的沙盒目录),让用户决定

步骤 1:建数据目录 + 起 git 仓库

mkdir -p "<BACKUP_ROOT>"

mkdir -p "<WORKTREE>"
cd "<WORKTREE>" && git init -q -b main

步骤 2:写白名单 .gitignore

复制 templates/gitignore-whitelist<WORKTREE>/.gitignore,然后把 <FILES> 里每一项相对 <WORKTREE> 的路径加 !<rel-path> 一行。

例:worktree=~/.ssh,要保护 ~/.ssh/config~/.ssh/known_hosts,就加:

!config
!known_hosts

步骤 3:初始 commit

cd "<WORKTREE>"
git add -A
git commit -q -m "chore: initial snapshot of <DOMAIN> config"

不要用 -c user.name/-c user.email 强制覆盖——让 git 用用户全局配置。仓库纯本地,不 push,提交人无关紧要。

步骤 4:把 .git 目录搬到 <BACKUP_ROOT> 并留 gitdir 指针

GITDIR="<BACKUP_ROOT>/<DOMAIN>-config.git"
mv "<WORKTREE>/.git" "$GITDIR"
echo "gitdir: $GITDIR" > "<WORKTREE>/.git"
git -C "$GITDIR" config core.worktree "<WORKTREE>"

步骤 5:复制 + 改写 auto-commit 脚本

复制 templates/auto-commit.sh<BACKUP_ROOT>/auto-commit.sh,替换 2 个占位符:

占位符替换为
__WORKTREE__<WORKTREE> 绝对路径
__LOG__日志路径,建议 ~/Library/Logs/config-file-versioning.log(macOS)或 ~/.local/state/config-file-versioning.log(Linux)

chmod +x 它。

步骤 6:装监听层(按操作系统分支)

6a. macOS(launchd LaunchAgent)

复制 templates/launchagent.plist,替换占位符:

占位符替换为
__LABEL__local.<USER>.<DOMAIN>-config-watchlocal. 前缀避免冲突;<USER>$USER
__SCRIPT_PATH__<BACKUP_ROOT>/auto-commit.sh 绝对路径
__WATCH_PATHS__<FILES> 里每个文件包一对 <string>...</string>,整体放在 <array>
__LOG__同步骤 5

写两份(保证目录可迁移):

  1. <BACKUP_ROOT>/<LABEL>.plist(备份模板)
  2. ~/Library/LaunchAgents/<LABEL>.plist(实际生效)

加载:

launchctl unload "$HOME/Library/LaunchAgents/<LABEL>.plist" 2>/dev/null
launchctl load   "$HOME/Library/LaunchAgents/<LABEL>.plist"
launchctl list | grep "<LABEL>"   # 验证已注册

6b. Linux(systemd path unit)

复制 templates/systemd.pathtemplates/systemd.service~/.config/systemd/user/,改名为 <DOMAIN>-config-watch.path<DOMAIN>-config-watch.service,按文件内的占位符替换。然后:

systemctl --user daemon-reload
systemctl --user enable --now <DOMAIN>-config-watch.path

6c. 其他 / 兜底

任何能"watch + run command"的工具都行:

  • gitwatch(跨平台 shell):brew install gitwatchapt install gitwatch
  • fswatch + 自管 daemonfswatch <FILES> | xargs -n1 <BACKUP_ROOT>/auto-commit.sh
  • inotifywait(Linux)+ shell 循环

把 watcher 进程托管到对应平台的服务管理器(OpenRC、runit、launchd、systemd)。

步骤 7:端到端测试(必做,不可跳)

改一个被监听文件,等比节流间隔多 2 秒,确认日志和 git log 各加一条。先注入临时字段,再清掉,期望产生 2 条 commit:

# 例:JSON 文件加临时键 _autoCommitTest
# sleep 12 (假设节流=10s)
tail -2 "<LOG>"
git -C "<WORKTREE>" log --oneline | head -3

步骤 8:写 README

每个 <BACKUP_ROOT> 都自带一份 README.md,便于以后迁移。最少包含:

  • 目的(为什么有这套)
  • 文件清单表
  • 日常命令(看历史、回滚、查 watcher 状态、看日志)
  • 维护(加新监控文件、改节流间隔)
  • 在新机器上重建步骤
  • 完整卸载步骤

反模式(明确避免)

❌ 错的做法✅ 正确
多个 config 域塞同一个 git 仓库一个域一个仓库
.git 留在 worktree 内搬到 <BACKUP_ROOT> 用 gitdir 指针
用黑名单 .gitignore用白名单
跳过端到端测试就交付必做,确认 commit 真的产生
把 plist/service 单元只放系统目录,不在 <BACKUP_ROOT> 留模板两处都放,保证目录自包含可迁移
commit 时 push 到公共远端禁止——配置可能含 token / 机敏信息,纯本地仓库
auto-commit.sh 里硬编码 git user.name/email留给 git 全局配置;脚本不该带个人信息
用 KeepAlive=true 跑 fswatch 常驻优先用事件驱动(WatchPaths / .path unit),无常驻进程

边界情况处理

  • worktree 目录不存在:先 mkdir -p。如果是首次,被监听文件本身可能也不存在——确认是否要先建空文件。
  • 被监听文件是符号链接:在 watcher 里同时监听链接路径目标真实路径,避免某些写入方式不触发其中一种。
  • 被监听文件是 binary:照样工作(git 存 blob),但 diff 看不出内容。
  • 同 Label/服务已存在:先 unload/disable 旧的再 load 新的。
  • 节流期内多次写入:节流窗口内的多次变更合并为单次 commit(这是想要的)。
  • 原子写(write-temp + rename):launchd WatchPaths 通常能正确触发;如个别程序的写法导致漏 commit,提示用户改用 KeepAlive + gitwatch / fswatch 主循环。

模板文件


设计哲学

  1. 可见 + 可读 + 可手改:版本史是文本 git,任何懂 git 的人都能定位/回滚,不依赖任何 GUI。
  2. 事件驱动 ≫ 轮询:减少 idle CPU/磁盘消耗。
  3. 失败安全:commit 脚本无副作用——cd 失败、diff 为空都静默退出,不会破坏 worktree。
  4. 目录自包含<BACKUP_ROOT> 里同时含数据 + 脚本 + plist/unit 模板 + README,整目录拷走能在新机器复活。
  5. 不 push:避免把私密配置上传到任何远程;想异地备份用文件级方案(Time Machine / Backblaze / 加密 syncthing)。

相关技能