# Keep an app in GitHub
Source: https://sovaria.in/docs/app-github-sync

Connect an app you build in Sovaria to a folder of a GitHub repository: push its files on demand or on every publish, and pull changes made in GitHub back.

An app made in the App Builder lives in Sovaria, with its own version history. Connect it to a repository and that folder becomes its home in GitHub too: your team reviews it there, CI runs on it, and changes made in GitHub can come back into the app.

(Diagram: One app, one repository folder — Push and pull are always started by you (or by Publish, if you turn that on).)

## Connect a repository

1. **Connect GitHub once** — Sovaria uses your own GitHub connection (**Functions › GitHub**). It needs push access to the repository. Your GitHub access stays with Sovaria's servers: it is never sent to the browser or to the app.
2. **Open the app's settings** — In the app studio, open **Settings › GitHub** and enter the repository (`owner/name`), the branch and, if the app is one folder of a bigger repository, that folder (for example `web`).
3. **Choose when to push** — Turn on **Push on every publish** to send each published version to GitHub (paid plans). Without it, push whenever you like with **Push to GitHub**.

If the folder already has code, Sovaria asks first: connecting means the app's files replace that folder's source files on the next push. To start from that code instead, import the repository as a new app (**Apps › New › Import from GitHub**).

## What a push does

| In the folder | After a push |
|---|---|
| Source files the app has (code, styles, HTML, JSON, Markdown…) | Written with the app's current version |
| Source files the app no longer has | Deleted, so the folder matches the app |
| Images, fonts, hidden files (`.env`, `.github/…`), `node_modules`, build output | Left as they are |
| Everything outside the folder | Left as it is |

Each push is one commit on top of the branch, never a force-push. If someone committed to the branch outside Sovaria since the app last matched it, the push stops and says so (`GITHUB_BRANCH_MOVED`): **Pull from GitHub** first to bring their changes in, open a pull request instead, or choose **Push anyway** — their commits stay in the history, but where both changed the same file the app's version wins. Pushing a version GitHub already has does nothing.

## Push on every publish

With **Push on every publish** on, each live publish of the app also pushes the published version, with the publishing person's GitHub access. A push that fails (for example, the branch moved) never undoes the publish: Publish reports it, and **Settings › GitHub** shows the last successful push.

## Pull changes back

**Pull from GitHub** reads the folder on the branch and saves its source files as a new version of the app. Earlier versions stay in the app's history, so a pull can be undone with **Restore**. **Open a pull request** puts the app's current files on a new branch and opens a pull request into the connected branch, for review before they land.

## From the API

HTTP:
```text
PUT    /api/v1/fn/hosting/frontends/{id}/github       {repository, branch, root, push_on_publish, replace_existing?}
POST   /api/v1/fn/hosting/frontends/{id}/github/push  {message?, force?}
POST   /api/v1/fn/hosting/frontends/{id}/resync       pull the folder back as a new version
POST   /api/v1/fn/hosting/frontends/{id}/pull-request {title, body?}
DELETE /api/v1/fn/hosting/frontends/{id}/github       disconnect (nothing in GitHub changes)
```

| Error | Meaning |
|---|---|
| `GITHUB_FOLDER_NOT_EMPTY` (409) | The folder has source files; connect with `replace_existing: true` or pick another folder |
| `GITHUB_BRANCH_MOVED` (409) | The branch has commits made outside Sovaria; pull first or push with `force: true` |
| `GITHUB_WRITE_DENIED` (403) | Your GitHub access can't push to that repository |
| `GITHUB_BRANCH_NOT_FOUND` (404) | No such branch in the repository |
| `PLAN_FEATURE_UNAVAILABLE` (403, `dev.git_sync`) | Push on every publish isn't on your plan; push by hand instead |
