Automate the Microsoft Fabric Git integration lifecycle from CLI environments using the Fabric CLI (fab api), with az rest as a fallback. Use when the user wants to connect a workspace to Azure DevOps or GitHub, commit or update/pull workspace items, check Git sync status, resolve update conflicts, disconnect, automate Git sync with a service principal, or link a branch workspace to its base. Do NOT use for branch switching, for deployment tools (fab deploy, fabric-cicd, Bulk Import), for dep...
Scanned 9/4/2026
Install to Claude Code
npx -y skills add paulasilvatech/awesome-harness-primitives --skill git-integration-operations-cli --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Git Integration Operations Cli?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/paulasilvatech-git-integration-operations-cli)More formats (shields.io, HTML) on the badges page.
---
name: git-integration-operations-cli
description: >
Automate the Microsoft Fabric Git integration lifecycle from CLI environments
using the Fabric CLI (fab api), with az rest as a fallback. Use when the user
wants to connect a workspace to Azure DevOps or GitHub, commit or update/pull
workspace items, check Git sync status, resolve update conflicts, disconnect,
automate Git sync with a service principal, or link a branch workspace to its
base. Do NOT use for branch switching, for deployment tools (fab deploy,
fabric-cicd, Bulk Import), for deployment-pipeline runtime, or for
cross-workspace reference rebinding after promotion (a property of the item
definition format, not the Git lifecycle). Triggers: "connect workspace to
git", "commit fabric to git", "update workspace from git", "fabric git
status", "resolve git conflict fabric", "service principal git sync", "link
branch to base".
metadata:
maturity: experimental
owner: yaron
---
> **Telemetry — MANDATORY.** Every `api.fabric.microsoft.com` call must carry
> `x-ms-fabric-skill: git-integration-operations-cli` (`az rest`: `--headers "x-ms-fabric-skill=git-integration-operations-cli"`),
> including every LRO poll, `fabric_lro` and retry. Snippets omit it — add it anyway.
> **CRITICAL NOTES**
> 1. To find a workspace ID from its name: list all workspaces, then filter by
> `displayName` with JMESPath. To find an item ID: list items of that type in the
> workspace, then filter by `displayName`.
> 2. All Git operations are **control plane** calls against
> `https://api.fabric.microsoft.com`. Run `fab auth login` once and `fab api`
> sets the base URL and Fabric token audience automatically. With the `az rest`
> fallback you must pass the matching `--resource` yourself, or you get a 401.
> 3. `commitToGit`, `updateFromGit`, and even `git/status` can return **HTTP 202
> (long-running operation)**, not just a synchronous body. On `202`, poll the
> operation: with `fab`, read `x-ms-operation-id` from `--show_headers` and poll
> `fab api operations/{id}` until `status` is `Succeeded`; with the `az rest`
> fallback, poll the `Location` / `x-ms-operation-id` header (see COMMON-CORE
> long-running operation polling). THEN read `git/status` to confirm
> `workspaceHead == remoteCommitHash`. Prefer the operation poll to confirm
> completion; if you poll `git/status` instead, treat an in-progress (`202`) or
> not-yet-synced response as "still running" and do not act on its `changes`
> until the heads match. (Learn: rest/api/fabric/core/git and
> fabric/cicd/git-integration/git-automation.)
> 4. `updateFromGit` requires the workspace's **current** `workspaceHead`; a stale
> value returns `400 WorkspaceHeadMismatch`. Always read `git/status` first.
> 5. **Prerequisites for every Git operation:** the workspace must be assigned to a
> **capacity** (an unassigned workspace fails with `WorkspaceHasNoCapacityAssigned`),
> and the caller (user or service principal) must hold the right workspace
> role: **connect and disconnect require Admin**; **commit and update require at
> least Contributor** with write permission on all items; **branch switching
> requires Admin** (or Contributor when the workspace opt-in setting *Allow users
> with at least Contributor role to change Git branch* is on). Only one Git
> operation may run against a workspace at a time.
> 6. **Tenant admin switches** gate Git integration and are a common cause of an
> unexplained failure. The **GitHub** sync switch is **off by default** (enable it
> before any GitHub connect); Azure DevOps sync is on by default. Cross-region
> workspace-to-*Azure*-repo needs the **cross-geo export** switch (GitHub does not
> enforce it). See [references/git-integration-concepts.md § Tenant admin prerequisites](references/git-integration-concepts.md#tenant-admin-prerequisites).
# Git Integration Operations — CLI Skill
Automate the Fabric Git integration lifecycle (connect, commit, update, status,
disconnect) from CLI environments. Whether cross-workspace item references rebind
after promotion is a separate concern, determined by the item definition format
(logical vs object IDs) rather than the Git lifecycle itself.
## Prerequisite Knowledge
Read these shared references first (paths assume this skill lives under
`skills/git-integration-operations-cli/` in `skills-for-fabric`; adjust when drafting
outside the repo):
- [COMMON-CLI.md § Finding Workspaces and Items in Fabric](../../common/COMMON-CLI.md#finding-workspaces-and-items-in-fabric) — **Mandatory** for resolving workspace/item IDs by name.
- [COMMON-CORE.md § Authentication & Token Acquisition](../../common/COMMON-CORE.md#authentication--token-acquisition) — wrong audience = 401; read before any auth issue.
- [COMMON-CLI.md § Authentication Recipes](../../common/COMMON-CLI.md#authentication-recipes) — `az login` flows and token acquisition for CLI.
- [COMMON-CLI.md § Fabric Control-Plane API via az rest](../../common/COMMON-CLI.md#fabric-control-plane-api-via-az-rest) — the `az rest` **fallback** path (**always pass `--resource`**); includes LRO polling helpers.
- [COMMON-CORE.md § Core Control-Plane REST APIs](../../common/COMMON-CORE.md#core-control-plane-rest-apis) — pagination, long-running-operation polling, rate limiting.
## Table of Contents
| Task | Reference |
|---|---|
| Pre-flight checks before connecting | [SKILL.md § Pre-Flight (before you connect)](#pre-flight-before-you-connect) |
| Connect a workspace to Git | [SKILL.md § Connect a Workspace to Git](#connect-a-workspace-to-git) |
| Create a git connection (service principal) | [SKILL.md § Create the Git provider connection (service principal)](#create-the-git-provider-connection-service-principal) |
| Commit workspace items to Git | [SKILL.md § Commit to Git](#commit-to-git) |
| Update a workspace from Git | [SKILL.md § Update from Git](#update-from-git) |
| Check sync status | [SKILL.md § Check Sync Status](#check-sync-status) |
| Resolve update conflicts | [SKILL.md § Resolve Conflicts](#resolve-conflicts) |
| Disconnect from Git | [SKILL.md § Disconnect from Git](#disconnect-from-git) |
| Link a branch workspace to its base (workspace relations) | [SKILL.md § Link a Branch Workspace to its Base (Workspace Relations)](#link-a-branch-workspace-to-its-base-workspace-relations) |
| Git integration concepts (sync model, status, permissions) | [references/git-integration-concepts.md § Concepts](references/git-integration-concepts.md#concepts) |
| Tenant admin prerequisites (switches) | [references/git-integration-concepts.md § Tenant admin prerequisites](references/git-integration-concepts.md#tenant-admin-prerequisites) |
| Supported Git providers | [references/git-integration-concepts.md § Supported Git providers](references/git-integration-concepts.md#supported-git-providers) |
| Supported item types | [references/git-integration-concepts.md § Supported item types](references/git-integration-concepts.md#supported-item-types) |
| Service principal / CI-CD pipeline templates | [references/automation-templates.md](references/automation-templates.md) |
| Gotchas, Rules, Troubleshooting | [SKILL.md § Gotchas, Rules, Troubleshooting](#gotchas-rules-troubleshooting) |
## Must/Prefer/Avoid
### MUST DO
- Read `git/status` and pass the current `workspaceHead` into every `commitToGit`
and `updateFromGit` call.
- Poll `git/status` after each async operation until
`workspaceHead == remoteCommitHash` **and** the `changes` array is empty. A
`202`/`Succeeded` status is not proof of a correct sync — validate the state.
- Deploy an item together with the items it references in the same commit, so
logical IDs can resolve inside the target workspace.
### MUST NEVER
- Never run two Git operations concurrently on the same workspace — a second
`commitToGit`/`updateFromGit` while one is still `Running` corrupts head
tracking. Serialize: poll the first to `Succeeded` before starting the next.
- Never treat a workspace as both a source of truth **and** a CI/CD push target
on the same branch. Pick one writer per branch: either humans commit from the
workspace, or automation pushes to Git — not both at once.
### PREFER
- `fab api` (after `fab auth login`) as the **primary** driver: it sets the base
URL and Fabric token audience automatically, avoiding the 401 wrong-audience
trap. Use `az rest` with `--resource "https://api.fabric.microsoft.com"` as the
**fallback** when `fab` is unavailable or you need fine-grained header control.
- `PreferRemote` conflict resolution for a clean "pull latest from Git" into a
downstream/target workspace.
### AVOID
- Reusing a stale `workspaceHead` (causes `400 WorkspaceHeadMismatch`).
- Passing request bodies via stdin to `fab api` (use `-i <file.json>` or `-i '<inline JSON>'`).
---
## CLI: `fab` primary, `az rest` fallback
Every operation below is shown with `fab api`. Run `fab auth login` once (it
supports service principals for automation); `fab` then handles the base URL and
token audience for you. The identical call on the `az rest` fallback is a
mechanical translation:
**Authenticate `fab` for the identity you are running as:**
```bash
fab auth login # interactive user (SSO)
fab auth login -u <client-id> -p <client-secret> --tenant <tenant-id> # service principal (CI/CD)
fab auth login --identity # managed identity (Azure compute)
```
A service principal or managed identity must be workspace **Admin** to connect or
disconnect (at least **Contributor** with write on all items is enough for
commit/update) and (for the `ConfiguredConnection` path) hold the git provider
credentials. See
[references/automation-templates.md](references/automation-templates.md) for full
pipeline scripts.
- `fab api <endpoint>` becomes `az rest --method GET --url "https://api.fabric.microsoft.com/v1/<endpoint>" --resource "https://api.fabric.microsoft.com"`.
- `fab api -X post <endpoint> -i body.json` adds `--method POST --headers "Content-Type=application/json" --body @body.json`.
`fab api` reads a request body via `-i` from either a file path or an inline JSON
string (e.g. `-i '{"displayName":"..."}'`) — never from stdin. It prints
`{"status_code": <code>, "text": <parsed body>}` (add `--show_headers` for a
top-level `headers` object), so read response fields with `jq -r '.text.<field>'`
and the LRO id with `jq -r '.headers["x-ms-operation-id"]'`. See
[Example 2](#example-2-az-rest-fallback-equivalents) for a full `az rest` pair.
---
## Pre-Flight (before you connect)
Run these Git-relevant checks in order before `git/connect`. Each failure has a
specific fix — resolving them up front avoids the most common connect-time errors.
| Check | Command | Pass = proceed / Fail = fix |
|---|---|---|
| Auth + Fabric token | `fab auth login` then `fab api workspaces` | `401` → re-login (`fab` sets the correct audience automatically) |
| Caller is workspace Admin | `fab api "workspaces/${WORKSPACE_ID}/roleAssignments"` | Connect/disconnect require the **Admin** role |
| Workspace has capacity | `fab api "workspaces/${WORKSPACE_ID}"` → check `capacityId` | `WorkspaceHasNoCapacityAssigned` → assign a capacity (Git requires one) |
| Git credential for the SP path | `fab api connections` (reuse if one already fits) | SP connect needs a `ConfiguredConnection`. If none exists, the skill **creates** it from your ADO org/project/repo + SP tenant/client/secret — see [Create the Git provider connection](#create-the-git-provider-connection-service-principal) (needs the tenant toggle "service principals can create connections"). The interactive-user SSO path uses `Automatic` and needs no connection. |
| Target `directoryName` exists on the branch | GitHub: `curl -fsS -H "Authorization: Bearer $PAT" "https://api.github.com/repos/$OWNER/$REPO/contents/$DIR?ref=$BRANCH"` — ADO: Items API on the repo | The API (unlike the portal) does **not** create a missing folder — connecting to a `directoryName` that doesn't exist fails with `404 GitProviderResourceNotFound`. If the folder is new, pre-create it first (see the note under [Connect a Workspace to Git](#connect-a-workspace-to-git)). |
## Connect a Workspace to Git
Connect a workspace to an Azure DevOps or GitHub repo, then initialize the
connection to establish the sync direction.
```bash
# 1. Connect (Azure DevOps example). Body goes in a file, passed with -i.
cat > connect.json <<'JSON'
{
"gitProviderDetails": {
"gitProviderType": "AzureDevOps",
"organizationName": "myorg",
"projectName": "MyProject",
"repositoryName": "fabric",
"branchName": "main",
"directoryName": "/workspace-a"
}
}
JSON
fab api -X post "workspaces/${WORKSPACE_ID}/git/connect" -i connect.json
# 2. Initialize the connection (choose which side wins on first sync)
printf '{"initializationStrategy":"PreferWorkspace"}' > init.json
fab api -X post "workspaces/${WORKSPACE_ID}/git/initializeConnection" -i init.json
```
Use `PreferWorkspace` when the workspace already has the authoritative items;
`PreferRemote` when the Git branch is authoritative.
> **The target `directoryName` must already exist on the branch.** Unlike the Fabric
> portal — which silently creates a missing folder by committing a `README.md`
> placeholder into it — the `git/connect` **API does not create the folder**.
> Connecting to a `directoryName` that does not exist on the branch fails with
> `404 GitProviderResourceNotFound`. The repo and branch must exist too (connect
> never creates a branch). If the folder is new, create it first by committing a
> placeholder file to the branch **through the Git provider's API** (not a Fabric
> API), then connect. Match the portal's convention with a `README.md` whose body is
> `This is an auto-created file via Fabric skills for <workspace-name>`.
>
> ```bash
> # GitHub: create <DIR>/README.md on <BRANCH> (reuse the PAT behind the Git connection)
> DIR="workspace-a"; WS_NAME="My Workspace"
> CONTENT=$(printf 'This is an auto-created file via Fabric skills for %s\n' "$WS_NAME" | base64)
> cat > mkfolder.json <<JSON
> { "message": "Create ${DIR} folder for Fabric Git connect", "branch": "${BRANCH}", "content": "${CONTENT}" }
> JSON
> curl -fsS -X PUT -H "Authorization: Bearer ${GITHUB_PAT}" \
> "https://api.github.com/repos/${OWNER}/${REPO}/contents/${DIR}/README.md" -d @mkfolder.json
> # Azure DevOps equivalent: POST .../_apis/git/repositories/${REPO}/pushes with an
> # "add" change for "/${DIR}/README.md" on refs/heads/${BRANCH} (same body content).
> ```
> **Two authentication modes (both providers use `myGitCredentials` for automation).**
> A workspace authenticates to git in one of two ways:
> - **Automatic git credential** (Azure DevOps only): the connecting user's SSO
> identity, established from workspace settings. Secondary contributors with at
> least Contributor reuse it. Not available for GitHub, and not usable for
> headless / service-principal automation.
> - **Configured credential** (Azure DevOps *and* GitHub): a Fabric cloud connection
> referenced in the connect body. This is the required path for any service
> principal or unattended run:
> ```json
> "myGitCredentials": { "source": "ConfiguredConnection", "connectionId": "<connection-guid>" }
> ```
> For **Azure DevOps**, the connection's `credentialType` can be either
> `ServicePrincipal` (headless automation) or `OAuth2` (a stored delegated user
> token); both are valid ConfiguredConnection credentials per the Create Connection
> API and both support multitenant scenarios.
> For **GitHub**, connecting a workspace **requires a Personal Access Token (PAT)**:
> the cloud connection stores a fine-grained token with Contents Read (plus Write
> to commit), or a classic token with the `repo` scope. GitHub has no automatic
> credential mode, so a PAT is mandatory even for an interactive user connect.
> The distinction is not "GitHub needs credentials, ADO does not". ADO *also*
> requires a ConfiguredConnection whenever a service principal drives the connect;
> the automatic mode only covers interactive user SSO. (Learn:
> fabric/cicd/git-integration/automate-git-integration-with-service-principal;
> rest/api/fabric/core/git/connect.)
> **Handle the initialize response.** `initializeConnection` returns
> `requiredAction` (`UpdateFromGit`, `CommitToGit`, or `None`) plus `workspaceHead`
> and `remoteCommitHash`, and can itself return `202`. After it settles, call the
> sync operation named by `requiredAction` with the returned hashes, or the
> workspace stays connected but not fully synced. (Learn:
> rest/api/fabric/core/git/initialize-connection.)
### Create the Git provider connection (service principal)
For unattended automation, first create a Fabric cloud connection that stores the
git provider credentials, then pass its `id` as the `connectionId` in the
`myGitCredentials` block above. Use the
[Create Connection API](https://learn.microsoft.com/rest/api/fabric/core/connections/create-connection)
(`POST /v1/connections`, delegated scope `Connection.ReadWrite.All`). A service
principal can create the connection only if the Fabric admin setting "service
principals can create workspaces, connections, and deployment pipelines" is on.
**Information you must supply (Azure DevOps):**
- ADO **organization**, **project**, and **repository** names, combined into one
`url` parameter: `https://dev.azure.com/<org>/<project>/_git/<repo>/`
- The service principal's **tenant ID**, **client (application) ID**, and
**client secret**
- A connection **display name** (max 200 chars, must be unique in the tenant)
**Prerequisites before the connection will work:** grant the service principal
(a) access to that **Azure DevOps organization and project** (git access is
enforced by ADO itself, not only by Fabric), and (b) **Admin** on the target
Fabric workspace.
```bash
# Create an Azure DevOps source-control connection using a service principal.
# The body holds a cleartext SP secret, so restrict perms and delete it on exit
# (a leftover connection.json can be captured by a later CI step or artifact upload).
trap 'rm -f connection.json' EXIT
umask 077
cat > connection.json <<'JSON'
{
"displayName": "ado-sp-fabric-cicd",
"connectivityType": "ShareableCloud",
"connectionDetails": {
"type": "AzureDevOpsSourceControl",
"creationMethod": "AzureDevOpsSourceControl.Contents",
"parameters": [
{ "dataType": "Text", "name": "url",
"value": "https://dev.azure.com/<org>/<project>/_git/<repo>/" }
]
},
"credentialDetails": {
"credentials": {
"credentialType": "ServicePrincipal",
"tenantId": "<tenant-id>",
"servicePrincipalClientId": "<client-id>",
"servicePrincipalSecret": "<client-secret>"
}
}
}
JSON
fab api -X post "connections" -i connection.json
```
The `201` response returns the connection `id`; use it as `connectionId` in the
`myGitCredentials` block of the connect call. For a fully secret-free variant,
pass the body inline with `-i '{...}'` (no file on disk) or reference a Key Vault
secret via `servicePrincipalSecretReference` instead of the raw
`servicePrincipalSecret`. The example above uses
`credentialType: "ServicePrincipal"`; Azure DevOps connections also accept
`credentialType: "OAuth2"` (a stored delegated user token), and both credential
types support multitenant scenarios. To switch an *existing* user-connected
workspace to a service principal, add the SP as workspace Admin, share or recreate
this connection, then
`PATCH /workspaces/{id}/git/myGitCredentials` with the same ConfiguredConnection
body. (Learn:
fabric/cicd/git-integration/automate-git-integration-with-service-principal;
rest/api/fabric/core/connections/create-connection.)
## Commit to Git
Push workspace items to the connected branch. Read the head first, then commit.
```bash
# Read current head. git/status can return 202 (LRO) with no body — capture the
# status code with --show_headers and poll before reading, or workspaceHead comes
# back as "null" and the commit below fails with a misleading 400.
RESP=$(fab api "workspaces/${WORKSPACE_ID}/git/status" --show_headers)
if [ "$(echo "$RESP" | jq -r '.status_code')" = "202" ]; then
OP=$(echo "$RESP" | jq -r '.headers["x-ms-operation-id"]')
until [ "$(fab api "operations/${OP}" | jq -r '.text.status')" = "Succeeded" ]; do sleep 5; done
RESP=$(fab api "workspaces/${WORKSPACE_ID}/git/status" --show_headers) # now 200 with body
fi
HEAD=$(echo "$RESP" | jq -r '.text.workspaceHead')
# Commit all pending changes. Build the body with jq so the comment is always
# valid JSON even if it contains a quote, backslash, or newline.
jq -n --arg head "$HEAD" --arg msg "Author items" \
'{mode:"All", workspaceHead:$head, comment:$msg}' > commit.json
RESP=$(fab api -X post "workspaces/${WORKSPACE_ID}/git/commitToGit" -i commit.json --show_headers)
# commitToGit may complete synchronously (200) or async (202). Only poll when async.
if [ "$(echo "$RESP" | jq -r '.status_code')" = "202" ]; then
OP=$(echo "$RESP" | jq -r '.headers["x-ms-operation-id"]')
until [ "$(fab api "operations/${OP}" | jq -r '.text.status')" = "Succeeded" ]; do sleep 5; done
fi
```
Use `"mode": "Selective"` with an `items` array to commit a subset. `commitToGit`
completes either synchronously (`200`, already settled) or asynchronously (`202`
with an `x-ms-operation-id` to poll) — branch on `.status_code` as above rather
than assuming `202`.
## Update from Git
Pull committed items into a workspace (for example, a downstream or target
workspace connected to the same branch/folder).
```bash
# Read both hashes from the target workspace
STATUS=$(fab api "workspaces/${TARGET_WS}/git/status")
TH=$(echo "$STATUS" | jq -r '.text.workspaceHead')
REMOTE=$(echo "$STATUS" | jq -r '.text.remoteCommitHash')
cat > update.json <<JSON
{
"workspaceHead": "${TH}",
"remoteCommitHash": "${REMOTE}",
"conflictResolution": {
"conflictResolutionType": "Workspace",
"conflictResolutionPolicy": "PreferRemote"
},
"options": { "allowOverrideItems": true }
}
JSON
fab api -X post "workspaces/${TARGET_WS}/git/updateFromGit" -i update.json
```
## Check Sync Status
Poll until the workspace and remote heads match. Both `commitToGit` and
`updateFromGit` are asynchronous, so this loop is how you know they finished.
```bash
for i in $(seq 1 30); do
S=$(fab api "workspaces/${WORKSPACE_ID}/git/status")
WH=$(echo "$S" | jq -r '.text.workspaceHead')
RC=$(echo "$S" | jq -r '.text.remoteCommitHash')
CH=$(echo "$S" | jq -r '.text.changes | length')
# Success = heads match AND no residual changes. Heads matching alone can still
# leave uncommitted/unapplied diffs; validate both before declaring "synced".
[ "$WH" = "$RC" ] && [ "$CH" = "0" ] && { echo "synced at $WH"; break; }
sleep 8
done
```
For an operation still in flight (a `202` from commit/update), poll the LRO
directly instead of guessing from heads:
```bash
# Capture x-ms-operation-id from the 202, then poll until Succeeded
OP_ID=$(fab api -X post "workspaces/${WORKSPACE_ID}/git/commitToGit" -i commit.json \
--show_headers | jq -r '.headers["x-ms-operation-id"]')
while :; do
ST=$(fab api "operations/${OP_ID}" | jq -r '.text.status')
case "$ST" in Succeeded) break ;; Failed) echo "op failed"; break ;; *) sleep 5 ;; esac
done
```
`git/status` also returns a `changes` array — a non-empty array means there are
uncommitted (workspace) or unapplied (remote) differences.
## Resolve Conflicts
A genuine conflict is *changed-on-both-sides*: after the last sync, the **same
item** was modified in the workspace (an uncommitted local edit) **and** on the
branch (a new commit). `git/status` flags that item in its `changes` array with
`conflictType: "Conflict"` (both `workspaceChange` and `remoteChange` show
`Modified`). This is distinct from a one-sided diff, where only `updateFromGit`
(remote-only change) or `commitToGit` (workspace-only change) is needed with no
policy.
When both sides changed an item, `updateFromGit` **requires** a conflict policy —
calling it without a `conflictResolution` block returns
`400 MissingWorkspaceConflictResolution`:
- `conflictResolutionPolicy: "PreferRemote"` — the Git branch wins (typical for
pull-into-target deployment); the local edit is discarded.
- `conflictResolutionPolicy: "PreferWorkspace"` — the live workspace wins; to
push the workspace version to Git instead use `commitToGit`, which does not
take a conflict policy.
Set `options.allowOverrideItems: true` to let the update overwrite items that
already exist in the target workspace (required whenever the conflicting item is
already present in the workspace, which it is by definition in a both-sides
conflict).
**Which policy to pick:**
| Scenario | Policy |
|---|---|
| Downstream/target workspace pulls the latest from Git | `PreferRemote` |
| The authoring workspace is the source of truth | `PreferWorkspace` (or `commitToGit` to push the workspace version to Git) |
| Genuine two-sided edits on the same item | Inspect each `changes` entry's `conflictType` in `git/status` and resolve deliberately per item |
## Disconnect from Git
```bash
fab api -X post "workspaces/${WORKSPACE_ID}/git/disconnect"
```
---
## Link a Branch Workspace to its Base (Workspace Relations)
> **Preview.** The **Git workspace relations** APIs are part of the Fabric Git
> surface and are in preview. They record a lineage link between a **branch**
> workspace and the **base** workspace it was branched from, so the base↔branch
> relationship is visible in the Fabric portal. Use them when a branch workspace
> was created **before** the built-in branch-out feature shipped, or when branch
> workspaces are provisioned by your own **automation** (not the portal branch-out
> UX) and you still want that lineage to show in the UI. Creating a relation does
> **not** connect either workspace to Git, move items, or start a sync — it only
> records the link.
**Precondition — both workspaces must point at the same Git root.** Create fails
with `WorkspaceRelationRootDirectoryMismatch` unless the base and branch workspaces
are connected to the **same repository root**, differing only by branch:
- **Azure DevOps:** same `organizationName`, `projectName`, `repositoryName`, and
`directoryName` — only `branchName` differs.
- **GitHub:** same repository URL and `directoryName` — only `branchName` differs.
So connect (and initialize) both workspaces to Git first; the relation just makes
the existing branch/base pair visible.
### Create a relation (link branch ↔ base)
The relation is **bidirectional**, so you create it **once**. Read the body
carefully — the workspace you POST to and the `relationType` are two different
things:
- The **URL** workspace (`workspaces/{workspaceId}/...`) is the side you call *from*.
- `relatedWorkspaceId` is the **other** workspace.
- `relationType` describes the role of that **other** (`relatedWorkspaceId`)
workspace, **not** the workspace in the URL. Only `Base` and `Branch` are valid
when creating.
So the same single link has two equivalent ways to create it:
| Call from (URL workspace) | `relatedWorkspaceId` | `relationType` |
|---|---|---|
| the **branch** workspace | the **base** | `Base` |
| the **base** workspace | the **branch** | `Branch` |
Do **not** run both — once one direction exists, creating the other fails with
`WorkspaceRelationBidirectionalExists`. Whichever side you call from, the
permission requirement is the same: **Admin on the branch workspace and Contributor
(or higher) on the base workspace.** The recommended (and most intuitive) form is
to call from the branch and point at the base:
```bash
# From the BRANCH workspace, declare that the related workspace is its Base.
printf '{"relatedWorkspaceId":"%s","relationType":"Base"}' "${BASE_WS}" > relation.json
fab api -X post "workspaces/${BRANCH_WS}/git/workspaceRelations" -i relation.json
```
Returns `201`. Service principals and managed identities are supported.
### List relations
Any **Viewer or higher** can list a workspace's relations (paginate via
`continuationToken`). `relationType` in responses can be `Base`, `Branch`, or
`RelatedWorkspace`.
```bash
fab api "workspaces/${WORKSPACE_ID}/git/workspaceRelations"
```
### Delete a relation
Removes the lineage link only — it does **not** delete either workspace or its
items. The caller must be **Admin** on the workspace named in the path (either the
base or the branch side). Only a `Branch` relation is deletable — otherwise the API
returns `WorkspaceRelationTypeNotBranch`.
```bash
fab api -X delete "workspaces/${WORKSPACE_ID}/git/workspaceRelations/${RELATION_ID}"
```
(Learn: rest/api/fabric/core/workspace-relations.)
---
## Related: cross-workspace item binding
Updating a target workspace from the same branch promotes items into it. Whether
each item-to-item reference rebinds automatically (logical IDs) or breaks (object
IDs / hardcoded endpoints) is a property of the item definition format, not of the
Git lifecycle, and applies equally to fabric-cicd and the Bulk Import API. To
predict a specific dependency's verdict, read the evidence-tagged compatibility
matrix, or empirically validate an uncertain reference, use the
**cross-workspace-item-binding** skill (a planned companion skill, not yet
available in this repo) — it is a distinct concern from the
connect/commit/update lifecycle this skill drives.
## Related: Terraform (infrastructure as code)
To manage the **connect** side declaratively (infrastructure as code) instead of
with imperative `fab api` calls, use the **git-integration-terraform** skill (a
planned companion skill, not yet available in this repo). It
covers the `microsoft/fabric` provider's `fabric_connection` and
`fabric_workspace_git` resources (Azure DevOps service principal or automatic,
GitHub PAT, init strategy, plan/apply/import). In that pattern Terraform owns the
connection, while the commit / update-from-git / status operations in this skill
remain the imperative runtime path — so a common setup is Terraform for the
connection plus a CLI step for update-from-git.
## Related: Variable Library (environment parameterization)
Git integration moves item *definitions* across workspaces, but it does not by
itself parameterize values that must differ per stage (dev/test/prod) — connection
strings, endpoints, capacity IDs, feature flags. That per-environment
parameterization is the job of a **Variable Library**: define the variables once,
provide a value set per stage, and have items resolve the stage-appropriate value.
When a Git-synced workspace needs values that vary by environment, use the
`variable-library-cli` companion skill for the variable/value-set
modeling instead of hardcoding or duplicating that logic here — this skill owns
the source-control lifecycle, the Variable Library skill owns the
parameterization it feeds.
## Gotchas, Rules, Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| `400 WorkspaceHeadMismatch` | Stale `workspaceHead` | Re-read `git/status`, retry with the current head |
| `400 MissingWorkspaceConflictResolution` | Both sides changed the same item, but `updateFromGit` was sent without a `conflictResolution` | Add `conflictResolution` (policy `PreferRemote` or `PreferWorkspace`) + `options.allowOverrideItems: true`; check `git/status` for `conflictType: "Conflict"` first |
| `WorkspaceHasNoCapacityAssigned` | Workspace not on a capacity (Git needs one) | Assign a capacity to the workspace, then retry |
| `404 GitProviderResourceNotFound` on `git/connect` | The repo, branch, or `directoryName` does not exist on the provider — the API, unlike the portal, does **not** auto-create the folder | Verify the repo/branch names; if the **folder** is new, pre-create it by committing a `README.md` placeholder to the branch via the Git provider API (see [Connect a Workspace to Git](#connect-a-workspace-to-git)), then retry |
| `WorkspaceAlreadyConnectedToGit` | Workspace is already linked to a repo | Disconnect first, then connect to the new repo |
| `InsufficientPrivileges` / `403` | Caller lacks the role the operation needs (connect/disconnect need **Admin**; commit/update need at least **Contributor** with write on all items) | Grant the caller the required role: Admin to connect/disconnect, or Contributor with write on all items to commit/update |
| `PrincipalTypeNotSupported` | Service principal using automatic credentials (or GitHub) | Switch to a `ConfiguredConnection` in `myGitCredentials` |
| `commitToGitRequest is required` (with `fab api`) | Body piped via stdin | Write the body to a file, pass `-i body.json` |
| Operation returns `202` then nothing changes | Async not yet complete | Poll `operations/{id}` (from `--show_headers`), or poll `git/status` until heads match |
| Operation stuck in `Running` for a long time | Large workspace or a second Git op is queued | Keep polling with a sane timeout (about 10 min); never start a second Git op concurrently |
| `401 Unauthorized` | Wrong token audience | Run `fab auth login` (fab sets the audience automatically); on the `az rest` fallback pass `--resource https://api.fabric.microsoft.com` |
| `WorkspaceRelationRootDirectoryMismatch` on `git/workspaceRelations` | Base and branch workspaces are connected to different Git roots | Connect both to the SAME repo/org/project/directory (Azure DevOps) or repo URL/directory (GitHub), differing only by `branchName`, then retry |
| `WorkspaceRelationAlreadyExists` / `WorkspaceRelationBidirectionalExists` | The base↔branch relation is already recorded (it is bidirectional) | Nothing to do — `GET git/workspaceRelations` to confirm; do not also create the reverse direction |
| `WorkspaceRelationTypeNotBranch` on delete | Tried to delete a relation that is not a `Branch` relation | Only `Branch` relations are deletable — use the correct `workspaceRelationId` and delete from the branch side |
## Examples
### Example 1: Commit source, then deploy into a target workspace
```bash
# Commit everything in the source workspace (jq builds a JSON-safe body)
SRC_HEAD=$(fab api "workspaces/${SRC_WS}/git/status" | jq -r '.text.workspaceHead')
jq -n --arg head "$SRC_HEAD" --arg msg "Deploy v1" \
'{mode:"All", workspaceHead:$head, comment:$msg}' > commit.json
fab api -X post "workspaces/${SRC_WS}/git/commitToGit" -i commit.json
# ...poll src status until synced, then updateFromGit into ${TARGET_WS} (see above)
```
### Example 2: `az rest` fallback equivalents
When `fab` is unavailable, the same REST endpoints work through `az rest`. Always
pass `--resource`; a body can be inline (`--body '{...}'`) or from a file
(`--body @file.json`):
```bash
# status
az rest --method GET \
--url "https://api.fabric.microsoft.com/v1/workspaces/${WORKSPACE_ID}/git/status" \
--resource "https://api.fabric.microsoft.com"
# commit (body inline)
az rest --method POST \
--url "https://api.fabric.microsoft.com/v1/workspaces/${WORKSPACE_ID}/git/commitToGit" \
--resource "https://api.fabric.microsoft.com" \
--headers "Content-Type=application/json" \
--body "{\"mode\":\"All\",\"workspaceHead\":\"${HEAD}\",\"comment\":\"Author items\"}"
```
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!