sync.js 现在支持两种源路径:
- 同步整个目录
- 同步单个文件
也就是说,--source 不再只支持目录,它现在支持:
- 远端目录 → 本地目录
- 远端单文件 → 本地单文件
- 远端单文件 → 本地目录
并且如果是文本冲突,脚本会尽量像 Git 一样,直接把冲突标记写进目标文件:
<<<<<<< LOCAL:...
你的本地内容
=======
远端内容
>>>>>>> REMOTE:...
这是你刚刚提到的重点:
只拉取某个文件也是可以的。
现在脚本已经按这个思路支持了,而且规则比较明确。
如果 --source 是单文件,且 --target 不是已存在目录,也不是以 / 结尾,那么:
--target会被当成最终文件路径- 这意味着你可以把远端单文件同步到本地不同名字的文件
例如:
node sync.js \
--repo template-remote \
--branch release \
--source packages/config/src/theme.ts \
--target projects/sandbox/src/ui/theme/app-theme.ts这表示:
- 远端是
packages/config/src/theme.ts - 本地目标是
projects/sandbox/src/ui/theme/app-theme.ts - 文件名可以不同
如果 --source 是单文件,且 --target 满足下面任意一个条件:
--target本身就是一个已存在目录--target以/结尾
那么脚本会:
- 把
--target当成目录 - 保留远端原文件名,放进这个目录里
例如:
node sync.js \
--repo template-remote \
--branch release \
--source packages/config/src/theme.ts \
--target projects/sandbox/src/ui/theme/结果会落到:
projects/sandbox/src/ui/theme/theme.ts
如果 --source 是目录,那么 --target 也按目录处理。
例如:
node sync.js \
--repo git@example.com:demo/blueprint-kit.git \
--branch main \
--source packages/widgets/src/elements \
--target projects/sandbox/src/ui/elements这表示把远端 packages/widgets/src/elements 下面的内容,同步到本地 projects/sandbox/src/ui/elements。
脚本执行时,按下面的规则处理:
- 远端有、本地目标里没有的文件:直接新增。
- 远端有、本地也有,而且内容完全一样:跳过。
- 远端有、本地也有,而且都是文本文件:
- 能自动合并的:直接自动合并。
- 不能自动合并的:直接把
<<<<<<</=======/>>>>>>>写进目标文件。
- 如果遇到结构冲突,脚本不会乱写内容,而是只在控制台和报告里提示你手动处理。
- 如果遇到二进制文件冲突,脚本会生成远端副本,并把副本路径写到控制台和报告里。
为了让后续同步更像真正的“三方合并”,脚本会把“上一次成功看到的远端版本”保存在:
.git/sync-state/
注意:
- 这个目录在
.git里面 - 不会出现在你的工作区文件列表里
- 不会被 Git 当成项目代码变更
这样第二次、第三次同步时,冲突判断会比“纯两份文件硬比”更准确。
请先确认以下几点:
- 你当前在这个仓库根目录:
/ - 你的 Node 版本是
>= 20.10.0 - 你对远端仓库有拉取权限
- 最好先把当前仓库代码提交一次,或者至少
git status看一下,确认没有重要未提交改动
如果你当前工作区有未提交改动,脚本默认会拦住你,避免误操作。
目录模式预览:
node sync.js \
--repo git@example.com:demo/blueprint-kit.git \
--branch main \
--source packages/widgets/src/elements \
--target projects/sandbox/src/ui/elements \
--dry-run单文件模式预览:
node sync.js \
--repo template-remote \
--branch release \
--source packages/config/src/theme.ts \
--target projects/sandbox/src/ui/theme/theme.ts \
--dry-run目录模式:
node sync.js \
--repo git@example.com:demo/blueprint-kit.git \
--branch main \
--source packages/widgets/src/elements \
--target projects/sandbox/src/ui/elements单文件模式:
node sync.js \
--repo template-remote \
--branch release \
--source packages/config/src/theme.ts \
--target projects/sandbox/src/ui/theme/app-theme.ts执行完成后,脚本会输出:
- 源路径类型是目录还是单文件
- 新增了多少文件
- 跳过了多少相同文件
- 自动合并了多少文件
- 写入了多少个 Git 风格冲突标记
- 生成了多少个二进制冲突副本
- 有多少个文件需要你额外手动处理
如果你不想每次都在命令行里写一长串参数,也可以直接修改 sync.js 顶部的默认配置:
const DEFAULT_CONFIG = {
repo: "git@example.com:demo/blueprint-kit.git",
branch: "main",
sourceDir: "packages/widgets/src/elements",
targetDir: "projects/sandbox/src/ui/elements",
reportFile: "sync-report.json",
failOnDirty: true,
};改完后,直接运行:
node sync.js这样脚本就会直接使用你写在 DEFAULT_CONFIG 里的值,不需要再额外传:
--repo--branch--source--target
这种方式比较适合:
- 你经常同步同一个远端仓库
- 你经常同步同一个分支
- 你经常同步固定的源目录 / 文件 到固定目标位置
如果你的同步目标比较固定,这样会比每次手输命令方便很多。
如果你既写了 DEFAULT_CONFIG,又在命令行里传了参数,那么:
命令行参数优先级更高。
例如你在 sync.js 里写了:
const DEFAULT_CONFIG = {
repo: "git@example.com:demo/blueprint-kit.git",
branch: "main",
sourceDir: "packages/widgets/src/elements",
targetDir: "projects/sandbox/src/ui/elements",
reportFile: "sync-report.json",
failOnDirty: true,
};但你实际执行:
node sync.js --branch release --source packages/widgets/src/advanced那么本次实际生效的是:
repo: 用DEFAULT_CONFIG.repobranch: 用命令行里的releasesourceDir: 用命令行里的packages/widgets/src/advancedtargetDir: 用DEFAULT_CONFIG.targetDir
如果你要同步的是单个文件,也可以直接这样写:
const DEFAULT_CONFIG = {
repo: "template-remote",
branch: "release",
sourceDir: "packages/config/src/theme.ts",
targetDir: "projects/sandbox/src/ui/theme/app-theme.ts",
reportFile: "sync-report.json",
failOnDirty: true,
};然后执行:
node sync.js如果你采用 DEFAULT_CONFIG 方式,我建议还是按这个顺序:
- 先把配置写进
sync.js - 先执行一次:
node sync.js --dry-run- 确认输出没问题,再正式执行:
node sync.js这样更稳。
-
--repo- 远端 Git 地址。
- 也可以写成你已经配置好的 remote 名,比如
template-remote。
-
--source- 远端仓库里要取的路径。
- 支持目录,也支持单个文件。
- 例如:
packages/widgets/src/elements - 例如:
packages/config/src/theme.ts
-
--target- 当前仓库里要合并到的路径。
- 支持目录,也支持单个文件。
- 如果
--source是单文件,--target可以是不同名字的文件。
-
--branch- 远端分支名。
- 默认值:
main
-
--dry-run- 只预览,不真正写文件。
- 新手第一次务必先用这个。
-
--allow-dirty- 就算当前仓库有未提交改动,也允许继续执行。
- 不推荐新手直接用。
-
--report- 指定运行报告输出路径。
- 默认值:
sync-report.json - 必须是当前仓库内的相对路径。
-
--help- 查看帮助。
如果某个文件无法自动合并,正式执行后,它会直接变成这样:
<<<<<<< LOCAL:projects/sandbox/src/ui/elements/button.ts
const title = '本地版本';
=======
const title = '远端版本';
>>>>>>> REMOTE:packages/widgets/src/elements/button.ts你要做的是:
- 打开这个文件
- 看清楚
<<<<<<<到=======是本地部分 - 看清楚
=======到>>>>>>>是远端部分 - 手动整理成你最终想保留的内容
- 删除冲突标记行
- 保存文件
处理完之后,文件里就不应该再有:
<<<<<<<
=======
>>>>>>>
有两类情况,脚本不会直接往目标文件里写冲突标记:
例如:
- 远端希望写入:
a/b.ts - 但你本地目标里
a已经是一个文件,不是目录
这种情况没法正常往目标文件里写入冲突标记,所以脚本只会提示你手动处理。
例如图片、字体、某些非文本资源文件。
这类文件不适合插入 <<<<<<< 标记,所以脚本不会往原文件里乱写冲突标记,而是会额外生成一份远端副本。
默认情况下,副本会优先生成在目标文件旁边,例如:
logo.png.sync-binary-incoming
如果目标位置旁边没法安全落副本,脚本会退回到:
.sync-conflicts/
同时它也会:
- 在控制台里提示副本路径
- 在
sync-report.json的binaryCopies字段里记录下来
你后续只需要自己对比:
- 原目标文件
- 新生成的二进制副本
确认怎么保留,然后再手动处理即可。
正式执行后,默认会在仓库根目录生成:
sync-report.json
这个文件里会记录:
- 运行时间
- 远端仓库地址
- 分支名
- 源路径
- 源路径类型(目录 / 单文件)
- 目标路径
- 新增文件列表
- 自动合并文件列表
- 写入冲突标记的文件列表
- 生成的二进制副本列表
- 需要额外手动处理的文件列表
如果控制台输出太多,直接看这个报告就行。
建议你每次都按下面流程走:
git status如果有很多未提交改动,先提交或暂存。
node sync.js --repo <你的git地址> --branch main --source <远端路径> --target <本地路径> --dry-runnode sync.js --repo <你的git地址> --branch main --source <远端路径> --target <本地路径>执行完成后,可以全局搜索:
<<<<<<<
找到所有需要你手动处理的文本冲突。
如果报告里还有 manual 列表,说明这些不是普通文本冲突,脚本没法直接帮你插标记,需要你自己判断怎么处理。
git status
git diff按你的实际项目流程做验证,然后再提交代码。
说明你没有传完整参数,至少需要:
--repo--source--target
说明脚本检测到你本地有未提交代码。
处理方式:
- 推荐:先提交或暂存
- 不推荐但可用:追加
--allow-dirty
一般有三种可能:
--source路径写错了--branch写错了- 你拉的那个仓库/分支里确实没有这个路径
通常是:
- 远端仓库地址写错
- 你没有权限
- SSH key / Token 没配置好
先单独试一下:
git ls-remote <你的git地址>如果这一步都不通,先把 Git 权限问题解决掉。
这是脚本的安全保护。
因为目标路径和报告文件都要求位于当前仓库内,所以不允许写类似下面这种路径:
../outside-dir
node sync.js \
--repo git@example.com:demo/blueprint-kit.git \
--branch release \
--source packages/widgets/src/elements \
--target projects/sandbox/src/ui/elementsnode sync.js \
--repo git@example.com:demo/blueprint-kit.git \
--branch release \
--source packages/config/src/theme.ts \
--target projects/sandbox/src/ui/theme/app-theme.tsnode sync.js \
--repo git@example.com:demo/blueprint-kit.git \
--branch release \
--source packages/config/src/theme.ts \
--target projects/sandbox/src/ui/theme/如果你是第一次用,强烈建议:
- 先
git status - 再执行
--dry-run - 确认输出没问题后再正式跑
- 跑完后全局搜索
<<<<<<< - 再看
sync-report.json里的binaryCopies和manual项 - 全部处理完再提交
这样最稳,也最接近你想要的 Git merge 体验。
如果你只是想本地验证整条合并链路,不想走 git fetch,可以直接用:
node sync-local.js --source <本地源路径> --target <本地目标路径> [options]这个脚本的特点:
- 不依赖远端仓库
- 不会走
git fetch - 复用
sync.js的核心合并逻辑 - 如果你传了
--base,它会按三方合并来模拟 - 它不会把基线状态持久化到
.git/sync-state,每次运行都是独立模拟
你现在这个场景,推荐直接执行:
node sync-local.js --source sycn1.js --target sync.js --base sync.js --dry-run这条命令的意思是:
- 把
sycn1.js当成“新版本” - 把当前
sync.js当成“目标文件” - 再把当前
sync.js同时当成“基线版本” - 然后只做预览,不真正写文件
这非常适合用来验证:
- 能不能自动 merge
- 会不会出现
<<<<<<<冲突标记 - 二进制/结构冲突会怎么提示
不传 --base 就行:
node sync-local.js --source sycn1.js --target sync.js --dry-run这时脚本会按“没有历史基线”的方式来模拟。