跳过主要内容
D

dsh-repot

daoyan2142/dsh-repot

通过一个私有 GitHub / Gitee 仓库在机器之间搬运 DSH 环境:第三方插件(按版本重装,或携带 tarball / 源码目录)、四个 profile 配置文件、以及文件夹快照。还原时逐文件比对,本机改过的文件不覆盖,被替换的文件先备份,并按绝对路径、已登记的根、或显式确认三级解析落点;逃出根、穿越 junction、覆盖非本插件目录的写入一律拒绝。配置文件和文件夹内容原样上传(不做密钥脱敏),插件也不检查仓库是否私有;访问令牌保存在凭据服务里,首次启动会把 dsh-plugin-git-sync 留下的 GIT_SYNC_* 令牌复制过来。需要 Node 22.12 及以上。

安装

dsh plugin --profile web add github:daoyan2142/dsh-repot

README

dsh-repot

Carry a DSH environment from one machine to another.

The name is "repot": lift the plant with its root ball intact into a new pot. Plugins, configuration and folders travel through one private GitHub / Gitee repository; on the way back, every write must land on the expected path or it is refused.

中文说明 · Install · First run · Why paths cannot drift · Limitations


What it carries

CategoryCarriedHow
Plugins (registry install)yesname@version only by default; reinstall on restore, tarball optional
Plugins (file: linked source)yesthe source tree travels, restored as a relative spec
Plugins (file: pointing at a tgz)yesthe tarball travels as-is
Configurationyescordis.patch.yml / package.json / pnpm-lock.yaml / pnpm-workspace.yaml
Foldersyesa snapshot of the chosen directories, with a path anchor
Sessions / tasksnosessions are zstd-appended logs; wrong shape and size for git
Credentialsnothe PAT never leaves the local credentials service
Runtimesnodsh-runtimes is per-machine
Cachesnostorages, .plugin-manager/asar-out are rebuildable

Commits, branches and graphs are not here — better-sidebar's Git tab owns those.

Install

dsh plugin --profile <your profile> add dsh-repot

Or install from the marketplace and restart DSH.

Two entry points appear:

  • Settings → Environment carry — platform, token, repository, sync roots
  • Sidebar → Repot — upload / compare / apply

Without better-sidebar the settings page also hosts the actions.

First run

  1. Create a repository (private is fine) and fill in owner/repo and the branch. All three sets use it by default; to split them (plugins on Gitee, files on GitHub) name a per-set repository under Per-set repositories and leave the rest empty to inherit.
  2. Paste a Personal Access Token. Not your DSH password. GitHub needs repo; Gitee needs repository read/write. It is stored in the credentials service and never read back over HTTP.
  3. Register sync roots — the anchors that survive a machine change:
idpath
workD:\work
pluginsC:\Users\you\.dsh\plugins

At most three, never a drive root (D:\), the folder must exist, and roots may not nest. The id stays; the path is re-pointed on the new machine — that is the only thing you re-enter.

  1. Pick what to carry in the sidebar and press Upload.
  2. On the other machine: install the plugin, enter the same repository and token, register roots, press Compare, then Apply.

The three buttons

ButtonEffectRisk
Uploadwrites the selected content to the repository, one commitremote only
Comparereads the remote manifest and diffs it against this machineread-only
Applywrites to this machinechanges local files, needs confirmation

Folder restores raise a preflight dialog that lists every absolute path about to be written.

Why paths cannot drift

Each folder entry records three things:

absPath   D:/work/notes       absolute path on the source machine
rootId    work                a registered root
relPath   notes               path inside that root

Restore resolves in order:

LevelConditionBehaviour
1absPath exists hereuse it
2rootId + relPath exists hererelocate
3neither existscreate, after you confirm it

Every level then faces four hard refusals:

  1. the target must be inside a registered root — blocks .. escapes
  2. the nearest existing ancestor must still realpath inside that root — blocks junction escapes
  3. an existing target without this plugin's marker is refused — a directory you filled yourself is never eaten
  4. a content hash mismatch is refused

Pruning only removes files recorded by the previous marker, so your own additions survive. Before anything is overwritten or deleted, the previous content is copied to <profile>/.repot/backup/<stamp>/files/<id>/, and a file you edited since the last restore is never overwritten and never pruned — it is reported as a conflict and left exactly as you left it.

Folder diffs

A folder has no version number, so identity is the hash of its files. Two layers:

  • the manifest records one tree hash per folder, which is what "same or different" compares
  • every folder index (files/<id>/.repot-index.json) records a hash per file, which is what "which file" answers

Comparison therefore lists the actual files:

notes          +2 ~0 −0
  + plan.md
  + src/todo.ts
assets         +0 ~2 −1
  ~ logo.svg
  ~ tokens/colors.json
  − old-banner.png

+ the remote added it, ~ both have it with different content, − the remote no longer has it (a restore removes it, unless you edited it locally).

Indexes written by an older version have no per-file hashes; those folders fall back to the tree-level verdict and say so.

Three plugin shapes

ShapeStoredRestored by
registryname@version (default, near zero bytes)installBundle('name@version')
registry + carrythe tarballdownload, then installBundle(tgz)
file: sourcethe source treematerialise under <profile>/plugins/<name>

Anything above 20 MB defaults to reinstall; dsh-boot-suite ships tens of megabytes of video and audio, so carrying it is a choice, not a default.

file:D:/... specs are rewritten to file:./plugins/<name> on restore.

Safety boundaries

  • The PAT lives only in the credentials service; not in settings, responses, or commit messages.
  • Every request passes ctx.connection.requestRejection; cross-origin calls get 403.
  • Configuration restores back up the previous file to <profile>/.repot/backup/<stamp>/; folder restores do the same for every file they replace or delete, under backup/<stamp>/files/<id>/.
  • A file whose content no longer matches what this plugin last wrote is treated as yours: the restore skips it and reports it instead of reverting your edit.
  • Remote branch updates use force: false; a moved branch is reported, not overwritten.

Limitations

  • No session/task sync — wrong shape and size for git.
  • Gitee writes file-by-file — no dependable tree endpoint, so large sets are slow and deletions cannot ride the same commit.
  • No three-way merge — conflicts are refused and reported, never guessed. Fix a conflict by taking the local file out of the way (or restoring it) and comparing again.
  • A folder is all-or-nothing — the list shows individual files, but the tick is per folder; there is no "apply just this one file".
  • Comparison reads the folder — hashes come from the bytes, so comparing a 500 MB folder costs 500 MB of reads (it does not hold them in memory).
  • No multi-repository parallel work, submodules, or LFS.
  • The marketplace requires a repository at least one day old; a brand-new repository is held, not rejected.

Relationship to dsh-plugin-git-sync

dsh-repot supersedes it. On first start it reads <profile>/.git-sync/settings.json and the GIT_SYNC_* credentials once, migrates them, and leaves the old files untouched so a rollback still works. Do not enable both.

License

MIT

相关插件