> ## Documentation Index
> Fetch the complete documentation index at: https://sdk.docs.xano.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Git, locks & merges

> What to commit, how to resolve a xano.lock conflict, handling renames across branches, and the four things a reviewer has to check that a normal code review misses.

The git details behind the [shipping workflow](/ship/shipping-workflow): what to commit, the
one file the Xano SDK maintains for you, and what a reviewer checks that a normal code review misses.

<Accordion title="New to git? The commands the workflow uses" icon="graduation-cap">
  | Command | What it does |
  | - | - |
  | `git init` | Turn the current folder into a repo. Once, at the start. |
  | `git switch -c <name>` | Start a **branch**, a parallel copy of your work, so `main` stays safe. |
  | `git switch <name>` | Move between branches that already exist. `git switch main` goes back. |
  | `git add <paths>` | Choose which changed files go into the next commit. `git add -A` takes all of them. |
  | `git commit -m "…"` | Save those changes, with a message describing them. |
  | `git push` | Send your commits to GitHub so other people (and CI) can see them. |
  | `git pull` | Bring down other people's commits before you start work. |
  | `git diff` | Show what changed: in your working copy, or between two tags. |
  | `git tag <name>` | Name a commit, such as the one a release was cut from. |

  A **pull request** is how a branch gets reviewed before it becomes part of `main`. Push the
  branch, then open one with the [GitHub CLI](https://cli.github.com):

  ```bash theme={null}
  git push -u origin add-orders-api
  gh pr create --fill          # title and body from your commits
  ```

  Merging is a button on the pull request. For git itself, the [GitHub "Hello World"
  guide](https://docs.github.com/en/get-started/quickstart/hello-world) takes ten minutes, and
  [Learn Git Branching](https://learngitbranching.js.org) teaches the branch model interactively.
</Accordion>

## What goes in git

<Columns cols={2}>
  <Card title="Commit" icon="check">
    `xano/**` (your defs and **`xano/xano.lock`**), `package.json`, `package-lock.json`, and
    your frontend source.
  </Card>

  <Card title="Never commit" icon="ban">
    `.xano/auth.json`, `.env`, `.secrets.json`, `node_modules`, `frontend/dist`, and any
    exported bundle. A bundle carries your workspace's environment variable **values**.
  </Card>
</Columns>

`xanosdk login --local-auth` adds `.xano/auth.json` to `.gitignore` for you. A scaffolded
project already ignores the rest; if you built the repo by hand, this is the minimum:

```gitignore theme={null}
node_modules/
frontend/dist/
.xano/
.env
.secrets.json
workspace.json
*.bundle.json
```

<Warning>
  A pull writes environment variable **values** to `xano/.env` and documentation tokens to
  `xano/.secrets.json`, both ignored above, and only the names to `xano/.env.example`. Keep it
  that way: never move a value into `xano/workspace.ts` or anything else under `xano/`. Anything
  you commit there is in your git history permanently.
</Warning>

## `xano.lock`, and why it is in the repo

`xano/xano.lock` freezes your workspace's object identities: the guids a release matches on, plus
the `canonical` URL tokens of API groups and toolsets. It is what makes a release **update in
place instead of duplicating**.

It is worth understanding when it is merely convenient and when it is irreplaceable, because
that changes how carefully you treat a conflict:

| Situation | Is the lock recoverable? |
| - | - |
| Objects your project created and never renamed | **Yes.** The guid derives deterministically from the name, so a deleted lock regenerates identically. Here it is a cache. |
| Objects **adopted** from a live workspace (`lock import`) | **No.** The engine assigned those guids randomly and nothing in your code can re-derive them. |
| Objects **renamed** in code (`lock rename`) | **No.** The rename pins the original guid under a new name, so the derivation no longer reproduces it. |

You generally will not know which entries have diverged without looking, and a workspace
adopted wholesale from the UI is almost entirely divergent. So: **commit it, always.**

It also holds two records the code cannot rebuild: `landed`, what this project landed on each
workspace and tenant (what `deploy --to … --prune` deletes by), and `synced`, the baseline
`workspace diff` uses to say which side changed.

<Note>
  `npm run xano:check` in CI (`export --check --strict`, which implies `--frozen-lock`) fails the
  build if an export would *change* the lock, which catches someone who forgot to commit it, and
  stops a canonical minted in a throwaway container from silently changing your public URLs.
  Mint locally, commit the lock.
</Note>

## Resolving a `xano.lock` conflict

This is the most common merge conflict in a Xano SDK repo, and it is almost always benign: two
branches each **added** entries, and git cannot tell that the additions are independent.

A conflict is git telling you two branches changed the same lines and it will not guess which
wins. It marks the spot in the file and waits for you:

```text theme={null}
<<<<<<< HEAD
  "function:sendReceipt": { "guid": "3f2a…" },     ← what is already on your branch
=======
  "function:refundOrder": { "guid": "9c41…" },     ← what the branch you are merging adds
>>>>>>> feature/refunds
```

Here **both entries are wanted**: they describe different objects. Resolve the file as the
union of both sides:

1. Under `objects`, keep every entry from each side.
2. Under `landed`, keep every entry from each side.
3. Under `synced`, keep either side's baseline for a Xano branch, or delete it. A missing
   baseline only means `workspace diff` cannot say which side changed until the next deploy or
   pull.
4. Delete the markers and fix the commas.

```bash theme={null}
# …edit xano/xano.lock as above, and finish resolving your code conflicts in xano/…
npm run xano:check                     # fails on a lock entry nothing exports, or a changed lock
git add xano/xano.lock && git commit
```

<Note>
  Do not take one side with `git checkout --ours` or `--theirs`. It throws the other branch's
  entries away, and for an adopted or renamed object nothing in your code can bring them back.
  Every command refuses a lock that still holds conflict markers, duplicate keys or broken JSON,
  and says how to fix it.
</Note>

<Warning>
  Resolve the **code** conflict too, keeping both branches' defs. If one side's defs were dropped
  in the merge, `xano:check` fails on their lock entries, which no exported object matches. That
  is the signal to restore the defs, not to delete the entries.
</Warning>

### Making git resolve it for you

Git's `merge=union` driver keeps both sides' lines instead of raising a conflict. It works line
by line, not by JSON key, so on `xano.lock` it can leave a missing comma, or the same `synced`
key twice, and every command refuses that lock until it is fixed by hand. Resolve the conflict
yourself with the recipe above.

<Note>
  Do not use `merge=ours` or `merge=theirs` either, because either silently discards one branch's
  identities, which for an adopted or renamed object is unrecoverable.
</Note>

## Renames

Renaming an object in code looks like **a delete plus a create** to the engine, because the
guid derives from the name. Moving the lock entry is what keeps it a rename:

```bash theme={null}
# code: defineFunction({ name: "signup" }) → { name: "register" }
xanosdk export ./xano/index.ts    # stderr: lock entry "function:signup" matches no exported object…
xanosdk lock rename --entry=xano/index.ts function signup register
xanosdk export ./xano/index.ts    # emits signup's original guid under "register"
```

**Commit the resulting `xano/xano.lock` in the same PR as the rename.** A rename whose lock change
is committed separately is a delete-and-create in whichever release lands first.

### Two people renaming on different branches

The renames are independent (different entries), so this resolves like any other lock
conflict: take the union of both sides. What breaks it is a rename that reached `main`
without its lock entry; the second branch then exports against a lock that no longer describes
reality. If an export warns about an orphaned entry after a merge, that is the signal: run
`lock rename` for the object it names before releasing.

If both branches renamed **the same** object (one to `stock`, the other to `goods`), the union
holds one guid under two keys, and every command refuses it, naming both. Keep the name your
code exports and drop the other key. The guid stays under the name you keep:

```bash theme={null}
xanosdk lock rename --entry=xano/index.ts table stock goods                  # moves stock's identity onto goods
xanosdk lock prune --identity-only --yes table:stock --lock=xano/xano.lock   # or just drop it
```

<Warning>
  Never resolve a rename conflict by deleting the entry. The guid it holds is the only link to
  the live object; without it the next release creates a duplicate and leaves the original
  orphaned in your workspace.
</Warning>

## Reviewing a Xano SDK pull request

Review the TypeScript like any other code, plus four things a normal code review misses,
because they are invisible in a diff that looks routine.

<Steps>
  <Step title="A removed table column, or a removed table">
    This is **data loss on release**. A tenant deploy replaces the tenant with the release, and
    on a workspace tables are shared by every Xano branch, so either way the data is gone as
    soon as the release lands. A tenant deploy names what it drops in a confirmation that
    `--yes` skips, and `promote` refuses until someone adds `--allow-shared-schema-changes`.
    In CI, neither stops anyone. Catch it in review.
  </Step>

  <Step title="Unexpected churn in xano.lock">
    Expected: new entries for new objects; a moved entry for a rename; new `landed` and
    `synced` entries after a landing. Unexpected: entries disappearing, or a guid changing for
    an object nobody touched. Either means an identity is about to be lost, so ask before
    approving.
  </Step>

  <Step title="Secrets in the workspace config">
    Environment variable **names** belong in `workspaceConfig({ env })` with an empty value; the
    values live in the gitignored `xano/.env`. A PR that writes a real value into the config is
    adding it to your git history permanently.
  </Step>

  <Step title="The ephemeral URL actually works">
    The PR job leaves a live backend and frontend behind. Click it. A diff cannot tell you the
    thing runs.
  </Step>
</Steps>

<Tip>
  Run the plan before approving anything that touches a table:

  ```bash theme={null}
  git switch <the-pr-branch>
  xanosdk deploy ./xano/index.ts --to tenant:prod --dry-run   # or --to workspace, if it is production
  ```

  It sends nothing. It prints what this change would do to production, including every column
  it would drop.
</Tip>

## Did that release land?

Worth knowing when a CI job or a rerun leaves you unsure. Do not re-run a release write blind:
a `release create`, `tenant deploy` or `promote` whose answer was lost exits **9** and prints a
`resolveWith` command, and a retried `release create` can leave a second release under a
suffixed name. Run the `resolveWith` command, then look:

```bash theme={null}
xanosdk release list                 # was the release cut?
xanosdk tenant get prod              # which release the tenant last landed, and when
xanosdk workspace branch list        # with no tenants: did the landing branch appear?
```

To see whether the workspace runs what your code says, compare the two:

```bash theme={null}
xanosdk workspace diff ./xano/index.ts            # exits 2 when anything differs
xanosdk workspace diff ./xano/index.ts --json     # also says which side changed
```

Add `--branch <label>` to compare a Xano branch that is not live. Under `--json`, the
differences are split by which side moved since they last matched (the baseline under
`synced` in the lock): `changedThere` (edited in
Xano), `changedHere` (edited in this project), and `changedBoth` (edited on both sides, a
conflict to reconcile by hand).

`deploy --to workspace` reads the same baseline before it writes. It refuses to overwrite an
object that changed in Xano (exit 2, `SDK_WORKSPACE_CHANGED`) until you pull the change into
the project, or pass `--overwrite` to deploy over it. More in [Drift](/govern/drift).

## Next

[Rollback and recovery](/govern/rollback-and-recovery): what to do when a release that landed
has to come back out.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.