Take updates from upstream
AppointMe has no update command. Merge release tags if you cloned, or merge a regenerated copy if you used dotnet new, and keep your .NET code where upstream merges don't reach.
- You'll get
- An upstream release merged on a branch
- Time
- Minutes to hours, depending on divergence
- Touches
- Shared files, migrations, lockfile, API client
- Before you start
- A clean working tree and passing tests
Pick your path
Section titled “Pick your path”There’s no update command, script or bot. You merge upstream yourself, and how you do it depends on how you started.
Dependabot (.github/dependabot.yml) ships in every copy, but it only bumps NuGet, npm and GitHub Actions versions. It never brings in upstream code.
| You started by | Your names | How you update |
|---|---|---|
| Cloning or forking the repo | AppointMe.* |
Merge an upstream release tag. Git has a common ancestor to work from. |
dotnet new appointme -n … |
Your own | Regenerate the new version and merge it. Upstream’s diff uses AppointMe.* names, so it won’t apply to your files. |
| Copying the files into a new repository | Either | Record the version you started from once (shown below), then follow the matching path |
Read what changed first
Section titled “Read what changed first”CHANGELOG.md follows Keep a Changelog and Semantic Versioning:
# Changelog
All notable changes to AppointMe are documented here.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]- One section per release, newest first:
## [1.2.1] — 2026-09-27, with### Added,### Changedand### Fixedgroups.## [Unreleased]collects what’s onmainbut not tagged yet. - A release is a
v*tag, and the template package has the same version. A release can be packaging-only: 1.2.1 changed the nuget.org page and generates exactly the same project as 1.2.0. - The links at the bottom open GitHub’s compare view, for example v1.2.0…v1.2.1. Read the real diff there before you merge. For unreleased work, compare
v1.2.1...main.
If you cloned: merge a release tag
Section titled “If you cloned: merge a release tag”Add upstream as a remote, fetch its tags under their own prefix, and merge a tag on a branch.
-
Add the upstream remote. If
originalready points atbravodev-hub/appointme, rungit remote rename origin upstreaminstead, then add your own repository asorigin.Terminal window git remote add upstream https://github.com/bravodev-hub/appointme.git -
Fetch upstream’s tags under an
upstream/prefix. Your copy ofdevtest.ymldeploys on every pushedv*tag, so keep upstream’sv1.2.1out of your own tag names.Terminal window git fetch --no-tags upstream 'refs/tags/v*:refs/tags/upstream/v*'git fetch --no-tags upstream maingit tag -l 'upstream/*' -
Merge the tag on a branch. Merge release tags rather than upstream
main, which is the development branch.Terminal window git switch -c update/appointme-1.2.1git merge upstream/v1.2.1 -
Resolve conflicts by file type, using the table in the next section. Then run the checks in Verify it works.
-
Merge the branch into your
mainthrough a pull request, so your own CI runs on it.
If git answers refusing to merge unrelated histories, your repository doesn’t share upstream’s history, because you copied the files rather than cloning. Record the version you started from once. This merge changes no files:
git merge -s ours --allow-unrelated-histories -m "Record AppointMe v1.2.1 as the base" upstream/v1.2.1From then on, git merge upstream/<newer tag> brings in only what changed after that version. Name the version you really started from: name a later one and its changes are silently skipped.
Resolve conflicts by file type
Section titled “Resolve conflicts by file type”Most conflicts land in a few kinds of file, and each kind has one right way to resolve. In a renamed project, read Contoso.Booking for AppointMe in the paths.
| Files | Don’t | Do |
|---|---|---|
src/AppointMe.Frontend/src/api/client.ts, client.schemas.ts |
Merge hunks by hand | Take either side, and regenerate once the backend builds |
*/Database/Migrations/*, including <Context>ModelSnapshot.cs |
Edit upstream’s migrations, or trust a clean snapshot merge | Follow the migrations table below |
src/AppointMe.Frontend/package-lock.json |
Merge hunks by hand | Take upstream’s, fix package.json, run npm install on Node 24, check with npm ci |
Directory.Packages.props |
Drop a version below upstream’s | Keep the higher version of each package, plus your own entries |
src/AppointMe.Aspire/AppointMe.Aspire.csproj |
Leave the SDK version behind | Keep Aspire.AppHost.Sdk on the same version as the Aspire.* packages |
CLAUDE.md, README.md |
Merge line by line | Take upstream’s, then add your own sections back |
To regenerate the typed API client, take the incoming side and rebuild it. During a merge, --theirs is the branch you’re merging in:
git checkout --theirs -- src/AppointMe.Frontend/src/api/cd src/AppointMe.Frontendnpm run generate:apiFor the lockfile, the frontend’s CLAUDE.md asks for the npm major CI uses: Node 24 with npm 11. A lockfile written by a different major churns, and npm ci has rejected one before.
EF Core migrations
Section titled “EF Core migrations”Each module has its own DbContext, schema, migrations folder and snapshot, and DatabaseMigrationService applies all of them when the API starts. Trouble starts only when you and upstream both added a migration to the same context.
| Your situation | Do this |
|---|---|
| Your migrations are all in your own module’s context | Nothing. Upstream’s files merge in untouched. |
| You added a migration to an upstream context, and no shared database has it yet | Abort the merge, remove your migration with git (keep the model change), merge, then add your migration again |
| Your migration is already applied to a shared or production database | Don’t delete it. Keep both migrations, make sure the snapshot contains both changes, and add a new migration on top |
EF Core 10 doesn’t record the latest migration in the snapshot, so git can merge two diverged snapshots without any conflict. Microsoft’s migrations in team environments guide covers each case. Don’t run migrations remove after a bad merge: it restores the previous migration’s model, which can lack upstream’s changes.
To check a snapshot after merging, add a throwaway migration for that context, as in Database migrations. If its Up() is empty, the snapshot matches the model and you can delete it.
If you used dotnet new: merge a regenerated copy
Section titled “If you used dotnet new: merge a regenerated copy”Keep a template branch that holds nothing but generated output. Regenerate the new version onto it, then let git merge the difference into your code.
-
Create the
templatebranch, once. If your first commit is the untouched output (step 3 of Rename your project), branch from that commit:Terminal window git branch template <first-commit-sha>If it isn’t, generate the version you started from onto an orphan branch, and record that branch on
mainas already merged:Terminal window FROM=1.2.0 # the version you generated fromgit switch --orphan templatedotnet new install BravoDev.AppointMe.Templates::$FROMdotnet new appointme -n Contoso.Booking -o . --forcegit add -A && git commit -m "AppointMe $FROM, generated"git switch maingit merge -s ours --allow-unrelated-histories -m "Record template $FROM as the base" template -
Regenerate the new version onto
template. Use exactly the-nyou used the first time.git rmcomes first, so files that upstream deleted show up as deletions.Terminal window TO=1.2.1 # the version you're moving togit switch templategit rm -rq .dotnet new uninstall BravoDev.AppointMe.Templatesdotnet new install BravoDev.AppointMe.Templates::$TOdotnet new appointme -n Contoso.Booking -o . --forcegit add -A && git commit -m "AppointMe $TO, generated" -
Read the change in your own names.
git diff template~1 templateis the upstream compare view, already renamed. -
Merge it on a branch. Then resolve conflicts as in the table above, and run Verify it works.
Terminal window git switch maingit switch -c update/appointme-$TOgit merge template
For a small release, porting by hand from the compare view is often quicker. To take upstream main before it’s released, pack the template from a clone of upstream (If you cloned instead) and install that .nupkg in step 2.
Why a template branch instead of a patch?2 min · optional
Upstream’s diff names AppointMe.Crm, and your files are Contoso.Booking.Crm, so it doesn’t apply. Regenerating translates the change into your names. But a patch still fails wherever you’ve edited the same file, because it doesn’t know what the file looked like before your edits.
The template branch supplies that. Its previous commit is the output you started from, and that commit is also an ancestor of your main. Git can then run a three-way merge: it keeps your edits, takes upstream’s, and stops only where both changed the same lines.
Verify it works
Section titled “Verify it works”Run what CI runs before you merge the update branch. These are the checks in devtest.yml and the frontend’s CLAUDE.md:
dotnet build AppointMe.slndotnet test AppointMe.slndotnet run --project src/AppointMe.Api/AppointMe.Api.csproj --no-launch-profile -- codegen testcd src/AppointMe.Frontendnpm cinpm run checknpm run buildcodegen test generates and compiles every Wolverine handler in memory, with no database. Then start the stack with Aspire: the API applies upstream’s new migrations to your local database on startup, and a bad snapshot fails at that point.
Keep your code merge-friendly
Section titled “Keep your code merge-friendly”The fewer of upstream’s files you edit, the less there is to merge.
| Do | Because |
|---|---|
Put product features in your own modules under src/ |
Upstream never touches them. Registering one adds a single line to Program.cs. See Add a module. |
Keep your migrations in your own module’s DbContext |
Its snapshot belongs to you alone |
| Add slices next to upstream’s instead of rewriting them | Endpoints, permissions and handlers are discovered from each module’s assembly, so a new slice edits no shared file |
Put your team’s agent rules in .claude/rules/ or an imported file |
Upstream edits CLAUDE.md too. See Working with AI agents. |
| When you must change an upstream file, keep the change small and comment why | Small hunks merge; rewrites conflict |
| Never hand-edit the generated client or reformat upstream code | The result is conflicts with no meaning |
| Merge every release | Conflicts grow with the gap |
Why regenerate the API client instead of merging it?1 min · optional
client.ts and client.schemas.ts are orval output, and orval rewrites them completely on every run (clean: true). A hand-merged file can mix two backends’ contracts. Regenerating from the merged backend gives you a client that matches the code you’re about to ship, with your endpoints and upstream’s.
Troubleshooting
Section titled “Troubleshooting”| Symptom | Cause | Fix |
|---|---|---|
fatal: refusing to merge unrelated histories |
Your branch doesn’t share history with upstream or with template |
Record your starting version once with git merge -s ours --allow-unrelated-histories |
Type errors on imports from @/api/appointme or @/api/<your name> after the update |
Main renamed the generated client to src/api/client.ts |
Change your own imports to @/api/client and @/api/client.schemas |
The API won’t start: The model for context '…' has pending changes |
A migration snapshot merged wrong. EF Core 9 and later refuse to migrate when it does. | Fix the snapshot, then check it with a throwaway migration |
npm ci fails after the merge with Missing: … from lock file |
The lockfile was written by a different npm major | Run npm install again on Node 24 (npm 11), then npm ci |
Every file changes on template |
You regenerated with a different -n, or different casing |
Regenerate with exactly the original name |
| Pushing tags starts a devtest deploy | You pushed upstream’s v* tags to your remote |
Fetch upstream tags under the upstream/ prefix, as in step 2 |