This document describes the two primary approaches for feature branch development in Microsoft Fabric with Git integration, the tradeoffs of each, and how this repository implements the Branch Out pattern.
In this scenario, dev is the only workspace connected to Git. Feature workspaces are created independently and are not git-synced. Code is deployed into them using fabric-cicd.
How it works:
- Developer creates a feature branch in Git from
dev. - A standalone Fabric workspace is created for the feature (not connected to Git).
- A CI/CD pipeline uses
fabric-cicdto deploy items from the feature branch into the workspace. fabric-cicdhandles metadata replacement (workspace IDs, lakehouse IDs) viaparameter.ymlat deploy time.- Developer works in the Fabric UI, then commits changes back to the feature branch.
- PR is opened to merge the feature branch into
dev.
Positive: You can use fabric-cicd to update metadata because the target workspace is not git-synced. Deployed workspaces are only updated through script-based deployments — this is the recommended flow from fabric-cicd:
"Deployed branches are not connected to workspaces via GIT Sync. Feature branches are connected to workspaces via GIT Sync. Deployed workspaces are only updated through script-based deployments." — fabric-cicd Getting Started: GIT Flow
Issue: All development happens in the Fabric UI. Syncing changes back to the feature branch is manual and error-prone, especially for items that cannot be fully tracked in Git.
In this scenario, you use Fabric's Branch Out feature. The feature workspace is git-synced to the feature branch.
How it works:
- Developer uses the Fabric UI Source Control panel to Branch out to another workspace.
- Fabric creates a new branch and a new workspace, syncing all items automatically.
- The workspace is connected to the feature branch via Git Sync.
- Developer works in the workspace, commits changes directly to the feature branch.
- PR is opened to merge the feature branch into
dev.
Positive: Fabric moves all supported items to the feature workspace automatically. Development and source control are tightly integrated.
Issue: You cannot use fabric-cicd to deploy into a git-synced workspace. fabric-cicd pushes changes directly via Fabric APIs, which creates workspace drift — the workspace state diverges from what Git expects. When Git Sync next runs, it can overwrite the fabric-cicd changes or produce conflicts, destabilizing the workspace.
This is explicitly documented:
"Deployed branches are not connected to workspaces via GIT Sync ... Deployed workspaces are only updated through script-based deployments, such as through the fabric-cicd library." — fabric-cicd Getting Started: GIT Flow
The inverse is also true: git-synced workspaces should not be targets for fabric-cicd deployments.
Additionally, when branching out, only Git-supported items are available in the new workspace, and certain workspace settings are not copied:
"When branching out, a new branch is created and the settings from the original branch aren't copied. Adjust any settings or definitions to ensure that the new meets your organization's policies." — Basic concepts in Git integration: Branching out limitations
This repository uses Scenario B (Branch Out) for feature development. Since fabric-cicd cannot be used on git-synced workspaces, a Python script handles the metadata updates that would normally be done by fabric-cicd at deploy time.
When you branch out from dev, all Fabric items are copied to the feature workspace. However, several items contain hardcoded dev workspace and lakehouse IDs:
- Semantic Model (
expressions.tmdl) — Direct Lake connection URL contains dev workspace and lakehouse GUIDs. - Notebooks (
notebook-content.py) — META dependency blocks reference dev workspace and lakehouse GUIDs. - Variable Library (
variables.json) — Default value set contains dev IDs (this is expected and not modified).
Without intervention, the feature workspace semantic model points to the dev lakehouse, and notebooks with hardcoded dependencies attach to dev.
A Python script at scripts/workspace_swap.py handles the full lifecycle of feature branch environment management.
- Branch out from the Fabric UI Source Control panel.
- Clone/pull the feature branch locally:
git fetch origin git checkout <feature-branch-name> - Set up
.env(one-time per developer): copy.env.sampleto.envat the repo root and paste in your feature workspace and lakehouse GUIDs. The.envfile is gitignored. - Run the swap-to-feature script (preview first with
--dry-run):The script automatically:python scripts/workspace_swap.py --dry-run python scripts/workspace_swap.py- Detects the current branch name (no arguments needed).
- Reads dev IDs from
variables.json(the default value set). - Reads feature workspace/lakehouse IDs from
.env. If.envis missing or has empty/missing keys, the script exits with an error pointing at.env.sample— there is no interactive fallback. - Displays the planned swap and asks
Type YES (uppercase) to apply, anything else to abort.TypeYES(case-sensitive) to proceed; the run is dry only when--dry-runis also passed. - Creates a feature branch value set (e.g.,
valueSets/<branch-name>.json). - Adds the value set to
settings.json. - Rewrites the semantic model Direct Lake connection in
expressions.tmdl. - Rewrites notebook META dependency blocks in all
notebook-content.pyfiles. - Sweeps stale feature IDs from a previous swap if
.envwas changed since the last run (recovery pass). - Validates no dev IDs remain in critical files.
- Commit and push the changes to the feature branch:
git add -A git commit -m "Swap to feature workspace for <branch-name>" git push - Sync the feature workspace from the Fabric UI (Update from Git).
- Activate the feature value set in the Fabric UI: open the Variable Library → select the feature value set → activate it.
- Run the import data notebook to populate the feature lakehouse.
Before merging back to dev, all feature-specific changes must be reverted so dev IDs are restored:
- Run the swap-to-dev script (preview first with
--dry-run):The script automatically:python scripts/workspace_swap.py --swap-to-dev --dry-run python scripts/workspace_swap.py --swap-to-dev- Reads feature IDs from the branch value set.
- Reverts the semantic model connection to dev IDs.
- Reverts notebook META blocks to dev IDs.
- Deletes the feature value set file.
- Removes the feature entry from
settings.json. - Validates no feature IDs remain in critical files.
- Commit and push:
git add -A git commit -m "Swap to dev for merge" git push - Open a PR to
dev.
A GitHub Actions workflow (.github/workflows/check-pr-ready.yml) runs on every PR targeting dev. It verifies:
- The semantic model contains dev workspace and lakehouse IDs.
- Notebooks with lakehouse dependencies contain dev IDs.
- No feature branch value set files exist (only
Test.jsonandProd.jsonare allowed).
If any check fails, the PR is blocked until the developer runs workspace_swap.py --swap-to-dev.
The repo ships slash commands in .github/prompts/ that wrap the CLI. In Copilot Chat (Agent mode) you can type:
/swap-to-feature— swap repo IDs to your feature workspace/swap-to-feature-dryrun— preview the swap without writing files/swap-to-dev— swap IDs back to dev (run before opening a PR)/swap-to-dev-dryrun— preview the revert without writing files/check-pr-ready— run the CI-style readiness check locally
Copilot will execute the script in the VS Code integrated terminal and show you the output. The /swap-to-feature slash command moves the YES confirmation into the chat UI — you click YES or NO in chat and Copilot pipes the answer to the script so the terminal never blocks. This is useful when you are already working in Copilot Chat and want to stay in the same workflow without switching to the terminal.
Note: Copilot cannot auto-trigger the script on branch checkout. You still need to invoke a slash command or run it yourself after pulling a feature branch.
workspace_swap.py reads your feature workspace and lakehouse GUIDs from a .env file at the repo root. This file is gitignored — each developer maintains their own.
- Copy
.env.sampleto.env. - Open the feature workspace in Fabric. Find:
- Workspace ID: Workspace settings → About → Workspace ID.
- Lakehouse ID: open the lakehouse, copy the GUID from the URL (the segment after
/lakehouses/).
- Paste both into
.env. - Run
python scripts/workspace_swap.py(or/swap-to-featurein Copilot Chat).
If .env is missing, has empty values, or is missing either key, the script exits with a clear error pointing at .env.sample. There is no interactive fallback — .env is the single source of truth for swap-to-feature.
For swap-to-feature, the script always reads .env (the existing value-set file does not override it). The value set on disk is read by swap-to-dev (to know which feature IDs to revert) and by the recovery pass (to detect previously-applied stale IDs that need rewriting). Before any rewrite happens, the script displays the planned dev → feature change and waits for the user to type literal YES to confirm.
The script intentionally does not auto-discover IDs via the Fabric REST API. An earlier implementation matched workspaces by display name, which could silently pick the wrong workspace (e.g. matching the dev workspace itself), causing the swap to abort with no value set written. Explicit .env config avoids that class of bug.
The script uses an item type registry to manage which Fabric item types participate in branch environment management. Each registered type declares its file patterns, whether it needs ID rewriting, and which IDs to validate. Adding a new item type requires only a single registry entry — no other code changes are needed.
Not all item types need rewriting. Fabric items fall into two categories based on how they reference environment-specific resources:
- Actual IDs (e.g., Semantic Models, Notebooks): These embed real workspace and lakehouse GUIDs that differ per workspace. They must be rewritten when swapping to a feature workspace and reverted before PR.
- Logical IDs (e.g., Ontology, Data Agent): These reference other items via the
.platformlogicalId, which Fabric resolves at runtime within the current workspace. These are portable across Branch Out workspaces and need no rewriting.
| Item Type | Files | ID Type | workspace_swap.py Rewrites? |
parameter.yml Handles? |
Notes |
|---|---|---|---|---|---|
| SemanticModel | *.SemanticModel/definition/expressions.tmdl |
Actual workspace + lakehouse IDs | Yes | Yes | Direct Lake connection URL contains real GUIDs |
| Notebook | *.Notebook/notebook-content.py |
Actual workspace + lakehouse IDs | Yes (only if default_lakehouse present) |
Yes | META dependency blocks reference real GUIDs |
| Ontology | *.Ontology/**/DataBindings/*.json, *.Ontology/**/Contextualizations/*.json |
Lakehouse logicalId (b36b3bda-...) + zeroed workspaceId |
No — logicalIds are portable | Yes — logicalId replaced with $items.Lakehouse... for CI/CD |
Uses .platform logicalId, resolved by Fabric at runtime |
| DataAgent | *.DataAgent/**/datasource.json |
Ontology logicalId (58a6c8ed-...) + zeroed workspaceId |
No — logicalIds are portable | No — references Ontology by logicalId | Cross-item logicalId reference, no environment-specific IDs |
| VariableLibrary | valueSets/*.json, settings.json |
Dev lakehouse ID in default value set | Managed (creates/deletes value sets) | Yes — default value set lakehouse ID replaced | Value set files are created/deleted, not rewritten |
| File | Role |
|---|---|
scripts/workspace_swap.py |
Swap workspace IDs in tracked files between dev and feature; CI readiness check |
tests/test_workspace_swap.py |
Unit tests for the branch environment script |
data/fabric/Patterns_Variables.VariableLibrary/variables.json |
Default (dev) value set — read-only reference for dev IDs |
data/fabric/Patterns_Variables.VariableLibrary/valueSets/ |
Per-environment value sets (Test, Prod, feature branches) |
data/fabric/Patterns_Variables.VariableLibrary/settings.json |
Value set ordering |
data/fabric/Patterns_Semantic_Model.SemanticModel/definition/expressions.tmdl |
Direct Lake connection — rewritten by the script |
data/fabric/Import_Patterns_Data.Notebook/notebook-content.py |
Notebook with hardcoded lakehouse dependency — rewritten by the script |
data/fabric/Patterns_Ontology.Ontology/EntityTypes/*/DataBindings/*.json |
Ontology data bindings — validated (not rewritten) by the script |
data/fabric/Patterns_Ontology.Ontology/RelationshipTypes/*/Contextualizations/*.json |
Ontology contextualizations — validated (not rewritten) by the script |
data/fabric/Patterns_Data_Agent.DataAgent/Files/Config/draft/ontology-*/datasource.json |
Data Agent datasource — registered but not scanned (no dev IDs) |
.github/workflows/check-pr-ready.yml |
PR check to block feature IDs from merging to dev |
.github/workflows/run-tests.yml |
Runs unit tests on PRs when scripts or tests change |
data/fabric/parameter.yml |
Deploy-time parameterization for fabric-cicd (used in CI/CD, not by workspace_swap.py) |