feat(update): updates.parked_branch_strategy gates the in-place merge; switch stays the default

Adapts the in-place branch update from PR #89507 (@willfrombr) onto the
switch-by-default behavior: the deterministic switch path remains the
default so non-interactive updates (desktop, gateway, cron) never dead-end
on a merge conflict, and deliberate custom-branch users opt in with
updates.parked_branch_strategy: update_in_place. --switch-branch overrides
the in-place strategy for one run (deep feature branches that must not
accumulate update merge commits). Docs + config comments + tests cover
all three routes.

Co-authored-by: Willian Santos <285090322+willfrombr@users.noreply.github.com>
This commit is contained in:
Teknium
2026-08-21 14:49:27 -07:00
parent 4fad27a101
commit 70151dd549
4 changed files with 30 additions and 8 deletions

View File

@@ -0,0 +1 @@
willfrombr

View File

@@ -3265,6 +3265,25 @@ DEFAULT_CONFIG = {
# checkout stayed days behind main on a stale branch). Set false to
# never auto-switch.
"auto_switch_parked_branch": True,
# HOW a clean parked branch with unmerged commits is handled:
# "switch" (default) — switch to the update target; the commits
# stay on the branch (git checkout never
# discards committed work) and a loud notice
# names the branch + count. Deterministic —
# never conflicts — so desktop/gateway/cron
# updates always land on current code.
# "update_in_place" — for a deliberately maintained custom branch
# (local patches on top of main): merge
# origin/<target> INTO the branch instead.
# The checkout never moves and local commits
# survive; a conflict stops the update
# cleanly with nothing changed. A safety tag
# (pre-update-<stamp>) is left before the
# merge. `hermes update --switch-branch`
# overrides back to the switch path for one
# run (e.g. a deep feature branch that must
# not accumulate update merge commits).
"parked_branch_strategy": "switch",
# Refresh an already-installed cua-driver during `hermes update`.
# The refresh is best-effort and macOS-only. Turn this off if the
# upstream installer is not appropriate for the machine, for example

View File

@@ -89,14 +89,14 @@ def build_update_parser(subparsers, *, cmd_update: Callable) -> None:
action="store_true",
default=False,
help=(
"When the checkout sits on a branch carrying unmerged commits, "
"switch to the update target and update THERE instead of merging "
"the target into the branch in place. The branch is left exactly "
"as it was — no merge commit is written into its history. Use on "
"long-lived feature branches where an update-driven merge commit "
"would pollute the branch; the default in-place behaviour suits "
"branches that track the target with a small patch set. Still "
"refuses to touch a dirty tree."
"With updates.parked_branch_strategy: update_in_place configured, "
"override it for this run: switch to the update target and update "
"THERE instead of merging the target into the checked-out branch. "
"The branch is left exactly as it was — no merge commit is written "
"into its history. Use on long-lived feature branches where an "
"update-driven merge commit would pollute the branch. No effect "
"under the default strategy (switch), which already switches. "
"Still refuses to touch a dirty tree."
),
)
update_parser.add_argument(

View File

@@ -49,6 +49,8 @@ If the source checkout was left sitting on a feature branch (by tooling, a workt
- **Branch fully merged** (every commit already contained in `origin/main` — `git cherry` reports nothing unmerged): the update says so — `Checkout was parked on '<branch>' (fully merged) — switched back to main` — and stays on `main` afterwards.
- **Branch has unmerged commits** but the tree is clean: the update still switches to `main` so the update can proceed — this is what non-interactive callers (the desktop update button, gateway `/update`, cron) rely on, since they have no way to resolve a skip. Your commits are untouched: `git checkout` never discards committed work, and the update prints a loud notice naming the branch and commit count, plus the `git checkout <branch>` command to pick the work back up later.
If you *deliberately* run a custom branch (local patches maintained on top of main), set `updates.parked_branch_strategy: update_in_place` in `config.yaml`. The update then merges `origin/main` **into** your branch instead of switching away from it — the checkout never moves, your commits survive, and the running code advances. Fast-forward when possible; on divergence a true merge behind a `pre-update-<stamp>` safety tag, stopping cleanly (nothing changed) on conflict. `hermes update --switch-branch` overrides back to the switch path for one run — useful on a deep feature branch that must not accumulate update-driven merge commits.
When the parked branch has **uncommitted changes** (dirty tree), Hermes does **not** touch it. The code update is marked **SKIPPED** with a loud warning naming the branch, how far behind `origin/main` it is, and the exact commands to resolve — instead of pretending the update succeeded. The completion line always shows the actual branch and HEAD (`✓ Update complete! [main @ 30fcf9580]`) so drift is visible at a glance. Set `updates.auto_switch_parked_branch: false` in `config.yaml` to disable the auto-switch entirely (the skip warning still fires).
### Local changes on non-interactive updates