> ## Documentation Index
> Fetch the complete documentation index at: https://xanots.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.

Everything here applies where **your repo is the truth**. If your workspace is authoritative,
the working copy is disposable and git is not involved — see
[Workspace is the truth](/guides/workflow-workspace).

<Accordion title="New to git? Start here — the eight commands this page uses" icon="graduation-cap">
  This page assumes you have used git before. If you have not, here is the whole vocabulary
  you need for XanoTS, in the order you will meet it. Nothing else on this page uses a command
  that is not in this list.

  | 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 save. `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/GitLab so other people (and CI) can see them.         |
  | `git pull`             | Bring down other people's commits before you start work.                          |
  | `git diff`             | Show what you changed but have not committed yet.                                 |

  **The loop is: switch → edit → add → commit → push.** Repeat as often as you like; a commit
  is cheap and local until you push.

  **A branch** is the unit of work. You start one per change (`git switch -c add-orders-api`),
  commit to it freely, and it never affects `main` until it is merged. That is what makes it
  safe to experiment.

  **A pull request** (GitHub) or **merge request** (GitLab) is how a branch gets *reviewed*
  before it becomes part of `main`. It is not a git command — it happens on the website:

  <Steps>
    <Step title="Push your branch">
      ```bash theme={null}
      git push -u origin add-orders-api
      ```

      The `-u origin <name>` part is only needed the first time you push a given branch; after
      that, plain `git push` works. The output prints a URL — open it and you land on the
      "create pull request" (GitHub) or "create merge request" (GitLab) page with everything
      filled in.
    </Step>

    <Step title="Describe it, and paste your ephemeral URL">
      Say what changed and why. Paste the URL `xanots deploy` printed, so your reviewer can
      click through a running copy instead of reading a diff.
    </Step>

    <Step title="Someone reviews it, then it merges">
      They comment or approve; you push more commits to the same branch if needed. Merging is
      a button on that page. Afterwards, get the merged work locally:

      ```bash theme={null}
      git switch main && git pull
      ```
    </Step>
  </Steps>

  Both hosts have a CLI that skips the website entirely:

  <Tabs>
    <Tab title="GitHub (gh)">
      ```bash theme={null}
      git push -u origin add-orders-api   # push first
      gh pr create --fill                 # title + body from commits
      gh pr view --web                    # open in a browser
      ```

      Install: [cli.github.com](https://cli.github.com), then `gh auth login` once.
    </Tab>

    <Tab title="GitLab (glab)">
      ```bash theme={null}
      glab mr create --fill               # pushes AND opens the MR
      glab mr view --web                  # open in a browser
      ```

      Install: [gitlab.com/gitlab-org/cli](https://gitlab.com/gitlab-org/cli), then
      `glab auth login` once.

      <Note>
        GitLab calls it a **merge request** — same thing, and `--fill` here also **pushes the
        branch for you**, so you do not need a separate `git push` first.
      </Note>
    </Tab>
  </Tabs>

  **Working alone?** You can skip branches and pull requests entirely — commit straight to
  `main`. Everything else on this page still applies, because `xano.lock` conflicts and renames
  come from *merging*, which you are not doing. Come back when someone joins you.

  For anything beyond this, the [GitHub "Hello World" guide](https://docs.github.com/en/get-started/quickstart/hello-world)
  is 10 minutes and covers branches and pull requests properly, 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), **`xano.lock`**, `package.json`, `package-lock.json`, and your
    frontend source.
  </Card>

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

`xanots login --local` 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
*.bundle.json
```

<Warning>
  A pulled `xano/workspace.ts` carries environment variable **values** inline, because that is
  what a deploy sends. Read it before the first commit and move real secrets out — into backend
  environment variables read at runtime with `env(name)`. Anything you leave there is in your
  git history permanently.
</Warning>

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

`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.**

<Note>
  `--frozen-lock` in CI 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 XanoTS 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:

```
<<<<<<< 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. And you do not have to
get the merge right by hand, because `xanots export` rebuilds this file from your source:

```bash theme={null}
git checkout --ours xano.lock          # just clears the markers — the next step overwrites it
git add xano.lock
# …finish resolving your actual code conflicts in xano/…
xanots export ./xano/index.ts          # rewrites xano.lock cleanly from the merged source
git add xano.lock && git commit
```

<Note>
  `--ours` sounds like it throws the other branch's entries away, and on any other file it
  would. It is safe **only here**, and only because of the `export` two lines below: that
  command regenerates the whole lock from the merged code, so whatever you pick now is
  discarded either way. Its only job is to get the conflict markers out of the file.
</Note>

The export is the authority, not your hand-edit. It walks the merged source and writes every
entry it needs — so as long as **both branches' code** made it into the merge, both branches'
entries come back.

<Warning>
  Resolve the **code** conflict first, then export. If you export while `xano/` still has
  conflict markers in it, the build fails; if you export having accidentally dropped one side's
  defs, the lock is rewritten without their entries and the mistake is now committed in two
  places.
</Warning>

### Making git resolve it for you

Because the export can always rebuild the file, `xano.lock` is a reasonable candidate for a
union merge — git keeps both sides' lines instead of raising a conflict, and your next export
tidies it:

```gitattributes theme={null}
# .gitattributes
xano.lock merge=union
```

<Note>
  This trades a conflict for a possibly-messy file that the next `xanots export` normalizes. Do
  not use `merge=ours` or `merge=theirs` — 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" }
xanots export ./xano/index.ts    # stderr: lock entry "function:signup" matches no exported object…
xanots lock rename --entry=xano/index.ts function signup register
xanots export ./xano/index.ts    # emits signup's original guid under "register"
```

**Commit the resulting `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 both sides and re-export. 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.

<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 XanoTS 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**, and no flag is required for it to happen. The release
    preview names each column that would be dropped and asks — but by then the PR is merged.
    Catch it in review.
  </Step>

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

  <Step title="Secrets in xano/workspace.ts">
    Environment variable values live inline there. A PR that adds one 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>
  xanots release ./xano/index.ts --dry-run
  ```

  It sends nothing. It just prints exactly what merging this would do to production.
</Tip>

## A release is safe to re-run

Worth knowing when a merge or a rerun leaves you unsure whether something landed. Before
importing, `release` compares the bundle against the workspace object by object; when
everything already matches, **nothing is sent** — the plan prints `no changes`, the summary
reports `"upToDate": true`, and no `updated_at` moves.

So the recovery for "did that release actually happen?" is to run it again and read the plan.
