Git

Integration

Git integration in SXL Studio: syncing tokens and data with GitHub and GitLab, Push, Pull, branches, and connection setup.

Why Git integration

Git integration turns SXL Studio into a two-way bridge between Figma and the code repository. Designers update tokens in the plugin → Push → developers receive changes. Developers update JSON → designer Pulls → designs update automatically.

What gets synchronized

DataRepository path
Data (Database)dataPath/ — JSON, CSV files, mappings, images
TokenstokensPath/ — token JSON files, config.json, diff-id, code-connect.json

Supported providers

  • GitHub — github.com and GitHub Enterprise Server (GHES)
  • GitLab — gitlab.com and self-hosted instances

Git integration works through the provider's REST API (not git CLI). Connection uses a Personal Access Token.

Sync diagram: Figma ↔ SXL Studio ↔ Git

Setting up a connection

Step 1 — Create a connection

  1. Open the Sync panel in the plugin (icon in the bottom bar)
  2. Click New Sync
  3. Fill in the fields:
FieldDescriptionExample
ProviderGitHub or GitLabgithub
NameConnection name (for you)Design System
RepositoryRepository pathorg/design-system
BranchWorking branchmain
Access TokenPersonal Access Tokenghp_xxxx...
Data PathData folder in the repodata
Tokens PathTokens folder in the repotokens
Enterprise URLURL for self-hosted (optional)https://git.company.com
  1. Click Save
Git connection creation form

Step 2 — First Pull

After creating the connection, perform a Pull — the plugin will download all files from the specified folders.


Push — sending changes

What happens during Push

  1. The plugin collects all local changes:
    • Data (datasets, mappings, images)
    • Tokens (JSON files, config.json, diff-id, code-connect.json, diff-grid.*)
  2. Compares with the remote state (by SHA)
  3. Forms a single commit with changes
  4. Sends to the selected branch

How to Push

  1. Click the Push button in the bottom bar (or via the Git menu)
  2. The Commit Modal opens with an embedded inline side-by-side diff:
    • Left — list of changed files with status icons (+ / ~ / ×) and counts
    • Right — Monaco DiffEditor: Remote on the left, Local on the right. Click files in the list to switch views. Binary assets are marked in the list and skipped by the diff engine
    • Hunk navigation: F7 — next change, Shift+F7 — previous (or the arrow buttons in the top-right corner of the editor)
    • Below — the commit message field with provider and branch hints
  3. Enter a commit description
  4. Click Push

All text files are normalized to a trailing newline (POSIX convention). This eliminates "phantom" diffs on blank last lines when there are no semantic changes. Files that turn out identical after normalization are marked in the list with and open with an explanation instead of the DiffEditor — so the Push list stays honest.

If there's nothing to send, the plugin shows a notification. If there are tokens but no Tokens Path is set, it shows an error with guidance.

Scope changed

If since the last Pull you changed the connection's repository, branch, dataPath, or tokensPath, the Commit Modal shows a ⚠ Scope changed badge. Push is not blocked by an extra acknowledgement checkbox—read the warning and decide; usually you should Pull into the new scope first so you don't push with stale context against a different branch.

Pushing changes to the repository

Pull — receiving changes

Soft Pull vs Hard Pull

TypeWhenWhat it does
Soft PullRepeat pull in the same scope (branch, repo, paths unchanged)Downloads only changed files. Local data is preserved
Hard PullFirst pull, branch/repo/path change, or explicit requestClears all local data and downloads fresh

How to Pull

  1. Click the Pull button in the bottom bar
  2. The Pull Modal opens with an embedded inline side-by-side diff:
    • Left — list of incoming files with counts and statuses
    • Right — Monaco DiffEditor: Local on the left (current state), Remote on the right (what will arrive). For new files the left side is empty — that's exactly what "added" looks like
    • Navigation: F7 / Shift+F7 between changes, arrow buttons in the top-right corner
  3. Below you can see the last remote-branch commit (author, message, time)
  4. Click Pull

Hard Pull

When you need a full resync:

  • Use Hard Pull from the menu
  • All local data and tokens will be replaced with repository contents

Progress

Pull shows progress through phases:

  • listing — scanning the file tree
  • data — downloading data
  • tokens — downloading tokens
  • finalizing — finalizing
  • done — complete

Speed with many files

Data and token files are downloaded in parallel (a bounded number of concurrent Git requests) to speed up Pull on large trees without hammering the provider. For huge repos, tree listing and network latency may still dominate; narrow dataPath / tokensPath in the connection settings if needed.

Auto-export after Pull

If autoExportOnPull: true is set in config.json, token export to Figma Variables runs automatically after Pull.

Pulling changes from the repository

Branches

Switching branches

  1. Click the current branch name in the bottom bar
  2. A menu shows available branches
  3. Select the desired branch
  4. The plugin updates the connection setting
  5. Perform Pull to synchronize

Switching branches does not download files automatically — you need to Pull. The first Pull after switching is a Hard Pull.

Creating a branch

  1. Click the branch name → Create Branch
  2. Enter the new branch name
  3. Optionally: select a base branch (Create Branch From…)
  4. The plugin creates the branch via the provider's API
Creating and switching branches

Status indicators

The plugin's bottom bar shows:

IndicatorMeaning
Provider icon + branchCurrent active connection
🟢 Green dot on PullChanges available on remote
🟢 Green dot on PushLocal changes pending
SpinnerOperation in progress (pull/push/refresh)
Progress counterPull progress (downloaded / total)
⚠ Truncated tree bannerGitHub returned a partial tree (repo too large) — Push/Pull are blocked until dataPath / tokensPath is narrowed
⚠ Hard pull required (in the Pull modal)repo / branch / dataPath / tokensPath changed — the next Pull will wipe local data

Status auto-refresh

  • Background polling — every 60 seconds, but it's not a rigid setInterval: after any refresh (background, manual, or from local events) the next tick is rescheduled. This removes duplicates like "I pressed Refresh → the plugin fires another request 5 seconds later"
  • Conditional GET. GitHub uses ETag / If-None-Match → a 304 Not Modified response doesn't consume the primary rate limit. GitLab checks the branch head commit SHA in one lightweight request → if the SHA hasn't changed, the client reuses the cached tree without paginating through files. Idle cost is close to zero
  • Instant refresh on focus. When the plugin window becomes visible again (visibilitychange → visible / focus), the plugin triggers check-git-status with a 10-second throttle — this makes the Pull indicator react to external pushes without noisy round-trips
  • Any manual Refresh, as well as background polling and visibility triggers, bypass the tree TTL cache while keeping the conditional check (ETag / head-SHA). This means a fresh branch commit is visible to the plugin on the next tick — rather than after two missed ticks as it used to behave
  • Local operations (save, export, push, pull) trigger triggerGitStatusRefresh() in-process — no UI roundtrips
  • The GitClient is cached for 60 seconds by (connection, repo, branch, paths, token-signature), so a single Push or Pull doesn't re-fetch the tree multiple times
  • Each refresh publishes the fresh tree snapshot into the UI store immediately — the inline diff in Push/Pull modals opens without extra loading and reflects current changes, even if you never opened the Git Browser

Performance and resilience

  • Truncated tree protection. For very large monorepos, the GitHub API may return a partial tree (truncated flag). In that case SXL Studio cannot guarantee a complete list of deletions, so Push and Pull are disabled and a warning banner is shown in the bottom bar. Fix: narrow dataPath / tokensPath to the actual design-system folders
  • Trailing newline normalization. All text files (JSON, CSV, Vue, Code Connect) are normalized to a single trailing \n before push. This removes "phantom" diffs that appear when different editors add or strip the final blank line
  • Real deletions only. A file is marked as "deleted" in the Push modal only if it actually exists on remote. This prevents false diffs after local renames/cleanups
  • Full diff for configs. The inline diff covers not only tokens and datasets, but also code-connect.json, diff-id.*, diff-grid.*, config.json — so every change that goes into the commit is visible
  • Size guard for huge files. Files larger than 1 MB or over 10 000 lines aren't loaded into the inline DiffEditor — an "Inline diff disabled" placeholder is shown instead. The file still ends up in the commit; it's just easier to review in an IDE or the provider's UI. This keeps the plugin iframe from freezing on heavy JSON
  • Noop filter. If the local and remote versions are byte-identical after normalization, the file is still committed (its Git blob SHA differs), but no DiffEditor opens for it — a short placeholder is shown instead. Such files are flagged in the list with a marker

Conflicts

SXL Studio does not use git merge. Synchronization works via SHA blob API:

  • During Pull — local files are overwritten with remote contents
  • During Push — if the remote already has a newer commit, the API may return an error

Recommendation: use a standard workflow — one person Pushes, others Pull. For parallel work, use separate branches.