uSync.TreeBranch
17.0.0-alpha02
dotnet add package uSync.TreeBranch --version 17.0.0-alpha02
NuGet\Install-Package uSync.TreeBranch -Version 17.0.0-alpha02
<PackageReference Include="uSync.TreeBranch" Version="17.0.0-alpha02" />
<PackageVersion Include="uSync.TreeBranch" Version="17.0.0-alpha02" />
<PackageReference Include="uSync.TreeBranch" />
paket add uSync.TreeBranch --version 17.0.0-alpha02
#r "nuget: uSync.TreeBranch, 17.0.0-alpha02"
#:package uSync.TreeBranch@17.0.0-alpha02
#addin nuget:?package=uSync.TreeBranch&version=17.0.0-alpha02&prerelease
#tool nuget:?package=uSync.TreeBranch&version=17.0.0-alpha02&prerelease
uSync TreeBranch
🚨 ** EARLY DEVELOPMENT - TEST BEFORE USE** 🚨
This project is in early development, you should test it works for you before using in anger.
Makes an Umbraco development site aware of the git checkout it runs from. Each git worktree (for example a parallel Claude Code session) gets its own database, ports and shared media, and uSync imports when the commit changes.
Install
dotnet add package uSync.TreeBranch
That's the whole setup: there is nothing to add to appsettings.json or Program.cs. The package brings uSync with it, and only does anything on a development site (see below).
The first time a development site starts with the package, uSync imports everything from the uSync folder, so the database matches the files on the branch. With uSync's default ExportOnSave those already match, but if your team doesn't commit its uSync files, expect the database to be put back to what the files say.
Setup notes
If worktrees live inside the repo (Claude Code puts them in .claude/worktrees/), add that folder to .gitignore.
If the site's .csproj is also inside the repo root (the default dotnet new umbraco layout), exclude the worktrees from the build too. Otherwise the SDK's default globs pick up every .cs and .cshtml in them, and the main site fails to build with duplicate types and views as soon as a worktree exists:
<PropertyGroup>
<DefaultItemExcludes>$(DefaultItemExcludes);.claude/**</DefaultItemExcludes>
</PropertyGroup>
With Worktree:ShareMedia on, media uploaded on a worktree site is written to the main checkout's wwwroot/media, and stays there whether or not the branch is merged.
A fresh install (Seed: Empty, NameByBranch, or nothing to copy) needs an unattended user: Umbraco:CMS:Unattended:UnattendedUserName, UnattendedUserEmail and UnattendedUserPassword (environment variables work). Without one the site shows Umbraco's installer instead of failing to boot.
Calling .AdduSyncGit() on the Umbraco builder is still supported, but not needed.
Requirements
- .NET 10
- Umbraco CMS 17.3+
- uSync 17.5+
- A git repository
How it works
Everything happens inside the site at startup. Nothing is written to appsettings.json: the values go into an in-memory configuration source, so they work however the site is started (Visual Studio, Rider, dotnet run, an AI agent).
Development sites only
None of this runs unless the site is a development site:
- Umbraco's runtime mode (
Umbraco:CMS:Runtime:Mode) is notProduction, and Umbraco:CMS:Hosting:Debugistrue, or the ASP.NET environment isDevelopment.
A site deployed from a git clone, or run locally as Production, does nothing: no database changes, no imports, no clean up. It logs uSync Git: off with the reason. Git:Enabled overrides this either way.
In a linked worktree
- Local settings: settings files the site loads but git ignores (for example
appsettings.Local.jsonholding the connection string) don't exist in a new worktree. They are read from the main checkout instead, in the same place in the configuration order. - Database
- SQLite: the database file lives in the worktree's own
umbraco/Datafolder, so it is already separate. If it doesn't exist, it is copied from the main checkout's database with SQLite's backup API, which is safe while the main site is running. - SQL Server: the worktree uses
{database}_wt_{id}_{worktree}on the same server.{id}is a short id for the clone, kept in.git/usync-git-id, so two clones (or two sites) sharing a server never mix up each other's worktree databases. If it doesn't exist, the main database is copied withBACKUP ... WITH COPY_ONLYandRESTORE ... WITH MOVEinto the server's default data folders. - If there is nothing to copy (or
SeedisEmpty),InstallUnattendedis switched on so Umbraco installs a fresh database and uSync'sImportOnFirstBootfills it. This only happens when an unattended user is configured; without one, Umbraco shows its installer.
- SQLite: the database file lives in the worktree's own
- Media:
Umbraco:CMS:Global:UmbracoMediaPhysicalRootPathpoints at the main checkout'swwwroot/media, which is gitignored and so missing from the worktree. Skipped if you've set that path yourself. - Ports: a stable https/http pair in the 45000 range, picked from a hash of the worktree name. The same worktree gets the same ports on every run unless something else holds them. This overrides
launchSettings.json. If no pair is free, the site keeps its own urls.
If any of this fails, it's logged as an error and the site boots without the git setup. The setup is logged as it happens, so the reason is in the log even when Umbraco then fails to boot.
In every checkout
- uSync defaults:
uSync:Settings:ImportOnFirstBootis turned on, unless the site sets it, so a fresh clone or a new worktree database fills itself from the uSync folder. Turn this off withGit:uSyncDefaults: false. - Import on commit change: when Umbraco starts, the HEAD commit is compared with the commit that was last imported into this database (stored in Umbraco's key-value table, so a copied database brings it along). If they differ, uSync imports. A plain restart doesn't import. Only a full import (group
All, or the configuredImport:Group) with no errors records the commit; importing one group from the dashboard doesn't. While the uSync folder has uncommitted changes the import is skipped, because those are normally exports from this site. - Branch databases (off by default): with
NameByBranch, the database name comes fromTemplate, so switching branch switches database. A branch with no database yet gets a fresh unattended install. The default branch keeps the configured name, so turning this on doesn't move your existing database.
In the main checkout
- Tidy up: SQL Server databases carrying this clone's
_wt_{id}_marker, whose worktree git no longer knows about, are dropped in the background after startup. A worktree counts until git removes it from.git/worktrees(git worktree removeorgit worktree prune), so a moved worktree, or one on a drive that isn't connected, keeps its database. SQLite worktree databases go when the worktree folder is deleted.
The default branch
The default branch is, in order: Git:DefaultBranch if set, the branch origin/HEAD points at (set by git clone), then main or master if there's a local branch with that name. It decides two things:
- With
NameByBranch, it keeps the configured database name. If it can't be worked out, every branch is named and a warning is logged. - A worktree copies the default branch's database, whichever branch the main checkout is on. If that database doesn't exist, it copies the main checkout's current one.
Only the parts that are added (branch, environment, worktree) have characters other than letters, digits, _ and . changed to _. The configured name is never touched.
uSync settings the defaults rely on
Anything the site sets itself always wins. These are the uSync settings that matter, with uSync's own defaults, which already suit a git workflow apart from ImportOnFirstBoot:
| uSync setting | uSync default | Here |
|---|---|---|
ImportOnFirstBoot |
false |
true when unset (turn off with uSyncDefaults: false) |
ImportAtStartup |
None |
Leave it None. If it's on, uSync imports every boot and the commit check stands aside. |
ExportAtStartup |
None |
Leave it None, or a copied database can overwrite the branch's files. |
ExportOnSave |
All |
Backoffice changes in a worktree land as files on its branch. |
Claude Code hooks
None are needed. The site sets itself up when it starts, and logs the URL it runs on:
uSync Git: worktree site will run on https://localhost:45590;http://localhost:45591
With SQL Server, a deleted worktree's database stays until the main checkout's site next starts (and until git worktree prune if you deleted the folder by hand). If you want it gone straight away, a WorktreeRemove hook can drop {database}_wt_{id}_{name} (id is in .git/usync-git-id, name is the worktree folder, with anything other than letters, digits, _ and . changed to _).
Configuration
All optional. The defaults are shown.
"Git": {
"Enabled": null,
"uSyncDefaults": true,
"DefaultBranch": null,
"Db": {
"NameByBranch": false,
"Template": "{db}.{branch}"
},
"Worktree": {
"Enabled": true,
"Seed": "Copy",
"ShareMedia": true,
"ShareLocalSettings": true,
"Ports": true,
"PortRangeStart": 45000,
"RemoveOrphanDatabases": true
},
"Import": {
"OnCommitChange": true,
"Group": "All",
"SkipWhenDirty": true
}
}
| Setting | |
|---|---|
Enabled |
Turn everything on or off. Unset means on for development sites only (see above). |
uSyncDefaults |
Fill in unset uSync settings (ImportOnFirstBoot). |
DefaultBranch |
The default branch. Unset means work it out (see above). |
Db:NameByBranch |
Name the database after the branch, except on the default branch. |
Db:Template |
Database name. Placeholders: {db} (the configured name), {branch}, {env}. In the branch and environment, characters other than letters, digits, _ and . become _, so feature/x becomes feature_x. |
Worktree:Enabled |
Set the site up for worktrees at all. |
Worktree:Seed |
Copy the main checkout's database, start Empty (unattended install + first boot import), or None (leave it to you). |
Worktree:ShareMedia |
Serve media from the main checkout. |
Worktree:ShareLocalSettings |
Read gitignored settings files the site loads from the main checkout. |
Worktree:Ports |
Pick ports per worktree. |
Worktree:PortRangeStart |
Start of the range ports are picked from (500 pairs). |
Worktree:RemoveOrphanDatabases |
Drop SQL Server databases left by deleted worktrees. |
Import:OnCommitChange |
Import at startup when the commit has changed. |
Import:Group |
uSync handler group to import. |
Import:SkipWhenDirty |
Don't import while the uSync folder has uncommitted changes. |
Limitations
- SQL Server copies need
BACKUPandRESTOREpermissions, so not Azure SQL. LocalDB withAttachDbFilenameisn't copied (the file is attached); the worktree gets an empty install instead. - One site per worktree: ports are picked per worktree, not per site.
- The commit check uses HEAD only, so
SkipWhenDirty: falseis the way to import uncommitted uSync files you edited by hand. - On a fresh database both uSync's first boot import and the commit import are requested. uSync skips the second as a duplicate.
Merging parallel changes to the same document type
Two branches that each add an allowed child to the same document type (the usual "new page type" work) both write it as the next SortOrder in that type's .config file, so the merge always conflicts:
<<<<<<< HEAD
<ContentType Key="1179ffc9-..." SortOrder="8">eventPage</ContentType>
=======
<ContentType Key="bf61058f-..." SortOrder="8">faqPage</ContentType>
>>>>>>> feature/faq-page
Keep both lines and renumber the second (SortOrder="9"), then commit. The same applies to other lists in the file, such as compositions and allowed templates: keep every entry, and make the sort orders unique.
Changes from the v15 version
- Targets Umbraco 17, uSync 17 and .NET 10.
- The connection string is no longer written to
appsettings.json; it is set in memory on every boot, so branch naming follows branch switches, not just the first install. DeleteOnBranchChangeand the{ticks}placeholder are gone ({ticks}gave a new database on every install and is now ignored).- The last imported commit is kept in the database instead of
uSync/git.meta. - The commit-based startup import is switched back on, and only looks at the uSync folders for uncommitted changes.
Project structure
uSync.TreeBranch/
├── uSync.TreeBranch/
│ ├── Configuration/ # uSyncGitSettings ("Git" section)
│ ├── Git/ # GitContext: reads .git files, works before Umbraco is up
│ ├── Worktrees/ # configuration source, database copy/drop, port picking
│ ├── Notifications/ # commit-aware import, orphan clean up
│ ├── uSyncGitService.cs # LibGit2Sharp: commit, status
│ └── Composer.cs # applies the git setup and logs what it did
├── uSync.TreeBranch.Tests/ # xunit tests, run by the PR build
└── uSync.Site/ # example site
License
This project follows the same licensing as the main uSync project. Please refer to the uSync license for details.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- LibGit2Sharp (>= 0.31.0)
- Umbraco.Cms.Api.Management (>= 17.3.0)
- Umbraco.Cms.Persistence.Sqlite (>= 17.3.0)
- Umbraco.Cms.Persistence.SqlServer (>= 17.3.0)
- Umbraco.Cms.Web.Website (>= 17.3.0)
- uSync.BackOffice (>= 17.5.1)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 17.0.0-alpha02 | 0 | 10/30/2026 |
| 17.0.0-alpha01 | 0 | 10/30/2026 |