Skip to content

[Fix] Changing a site's branch no longer checks out the live release under modern deployment - #1263

Merged
saeedvaziry merged 1 commit into
vitodeploy:4.xfrom
kevsmir02:fix/branch-change-modern-deployment
Oct 5, 2026
Merged

saeedvaziry merged 1 commit into
vitodeploy:4.xfrom
kevsmir02:fix/branch-change-modern-deployment

Conversation

@kevsmir02

@kevsmir02 kevsmir02 commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

What happens

On a site with Modern Deployment enabled, changing the branch under Site → Settings → Branch rewrites the code of the live release in place.

UpdateBranch runs git fetch origin and git checkout -f <branch> in $site->path. With modern deployment, $site->path is <base>/current, the symlink to the active release, so:

  • the live release starts running the new branch's PHP straight away, with no build, no pre-flight script and no migrations. In our case the next request returned a 500 (column ... does not exist) until a deployment ran the migrations.
  • git checkout -f restores tracked files over the shared resources, so storage in that release turns into a plain directory instead of the symlink to <base>/source/storage. Uploads, logs and cache written by that release no longer reach shared storage.
  • <base>/source stays on the old branch, so the next deployment's git pull origin <new-branch> there moves the old local branch onto the new one.

Steps to reproduce

  1. Create a Laravel site, enable Modern Deployment (shared resources .env,storage) and deploy.
  2. Under Site → Settings, change the branch and save.
  3. On the server, git -C <base>/current reflog -1 shows checkout: moving from <old> to <new>, and <base>/current/storage is a directory.

Fix

When modern deployment is enabled, UpdateBranch runs the fetch and the checkout in <base>/source instead. Git::checkout() and Git::fetchOrigin() take an optional path, the way Git::clone() already does. The live release is left alone, and the next deployment clones the new branch into a fresh release as usual. Sites without modern deployment behave as before.

Tests

  • New test in tests/Feature/SitesTest.php: with modern deployment enabled, changing the branch runs in <base>/source and never cds into <base>/current. It fails on 4.x without the fix.
  • The full suite passes on Ubuntu 26.04 with PHP 8.5 (2,462 tests), and Pint and PHPStan are clean.
  • Also checked on a real server (Ubuntu 26.04, Vito at 661e497c plus this patch): after changing the branch twice, the active release's HEAD and reflog were untouched, its storage was still a symlink, and source switched branches.

…under modern deployment

With modern deployment enabled, a site's path is `<base>/current`, which
points at the active release. UpdateBranch ran `git fetch origin` and
`git checkout -f <branch>` in that path, so saving a new branch swapped
the code of the live release in place, with no build, no pre-flight and
no migrations. The forced checkout also restored tracked files over the
shared resources, replacing the `storage` symlink with a plain directory
in that release.

The branch is now switched in `<base>/source`, the checkout that holds
the shared resources and is pulled on every deployment. The next
deployment clones the new branch into a fresh release as usual. Sites
without modern deployment keep their current behaviour.
@coderabbitai

coderabbitai Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository: vitodeploy/vito/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Advanced

Run ID: 1ff07467-49bf-4ad2-91a9-b29e732a014a

📥 Commits

Reviewing files that changed from the base of the PR and between 661e497 and 3d1825b.

📒 Files selected for processing (3)
  • app/Actions/Site/UpdateBranch.php
  • app/SSH/OS/Git.php
  • tests/Feature/SitesTest.php

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 7 remain after this review.


📝 Walkthrough

Walkthrough

Branch updates now run Git fetch and checkout operations in the source directory for modern deployments. Git operations accept an optional path and retain the site path as the default.

Changes

Branch update path

Layer / File(s) Summary
Optional paths for Git operations
app/SSH/OS/Git.php
checkout and fetchOrigin accept an optional path. Both use the site path when no path is supplied.
Modern deployment branch updates
app/Actions/Site/UpdateBranch.php, tests/Feature/SitesTest.php
For modern deployments, UpdateBranch passes the source path to Git operations. The test checks that checkout runs from source, not current. The method documentation describes branch switching in the source checkout and cloning on the next deployment.

Priority: ➖ Normal

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix

Suggested reviewers: saeedvaziry

Merge Risk: ⚪ Minimal · up to 3d182

Modern-deployment branch changes now target the source checkout rather than the live release, while other deployments retain their existing behavior. The change is ready to merge subject to normal checks.

Security Architecture Review

Security architecture risk: 🟡 Moderate · up to 3d182

The change prevents checkout from directly rewriting the active release. However, the replacement directory also backs resources shared by running releases. Where shared resources overlap Git-tracked files, forced checkout can still change live configuration or data before deployment, and switching back to an older release would not restore those resources.

Retained concerns

  • Medium · security · inferred: Moving forced checkout into source does not fully isolate branch changes from live state. Releases link configured shared resources into source, so checkout can overwrite local tracked changes or change tracked shared files immediately. This newly reachable mutation can affect running and rollback releases without deployment validation; actual exposure depends on shared-resource and Git-tracked path overlap.
Security review details

Security Blast Radius

  • inferred — The changed caller targets the same Site and SSH identity rather than a request-selected cross-site path. Its filesystem effects can nevertheless reach every release linked to that Site's shared resources, including the active release and rollback candidates.

Security Findings and Attack Paths

  • inferred — A user able to update the Site branch can trigger forced checkout of repository content into source. If shared configuration or data overlaps tracked paths, selecting a branch can alter those resources before deployment validation. This is a conditional failure-containment concern, not a verified unauthorized-access exploit; production overlap and malicious repository content were not established.

Trust Boundaries and Controls

  • observed — The existing Site update authorization remains before action invocation. Model-derived path selection and shell quoting constrain the changed input flow; the optional path does not itself grant a new SSH identity or credential source.

Resilience and Maintainability Implications

  • inferred — Separate release directories contain application checkout and build failures, but shared-source mutation lies outside that containment boundary. Repeating a branch update or restoring an older release is not a demonstrated recovery mechanism for discarded shared-file changes.

Hardening Proposals

  • proposed — Separate live shared resources from the forcibly checked-out worktree, or enforce and verify protection against tracked-path overlap before checkout. Treat shared-resource preservation as part of branch-change recovery rather than relying on release rollback.
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarises the main change: branch updates no longer check out the live release when modern deployment is enabled.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Autopilot is currently an internal CodeRabbit preview.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@saeedvaziry
saeedvaziry merged commit 67fd6a7 into vitodeploy:4.x Oct 5, 2026
5 of 6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants