# Source control

One authoritative Git repository per app: DeepSpace source, GitHub source, and how to move between them.

Every app has **exactly one authoritative Git repository**. Which one is a server-enforced setting, not an inference from remote names — so before deciding anything from what `git remote -v` shows, ask the platform:

```bash
npx deepspace app source --json
```

## The two providers

| Source        | Experience                                                                                                                                                                                                                         |
| ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **DeepSpace** | The packaged default. Commit-first: deploy publishes the attached clean branch automatically, and the `space` remote, `clone`/`push`/`pull`, [workspaces](/guides/workspaces), activity, releases, and rollback all work together. |
| **GitHub**    | Manual ownership. You push with ordinary Git; an ordinary deploy ships the **local working tree** and performs no Git read, write, or verification.                                                                                |

Under GitHub source, dirty and unpushed bytes are **valid deploy inputs**. The release records the configured repository and source revision but carries no DeepSpace commit id; rollback uses the retained deployment bundle. Do not add Git checks to this path — selecting GitHub means the developer owns synchronization, and the platform respects that.

Seeing both an `origin` and a `space` remote — or retained Git objects on the inactive provider — does **not** mean two sources of truth. Server policy enables writes only on the selected provider. Releases and activity remain platform facts under either source.

## Selecting or transferring authority

```bash
npx deepspace app source github [--remote <name>]
npx deepspace app source deepspace [--remote <name>]
```

`--remote` names the local GitHub remote to verify or import when more than one exists (default `origin`).

An app with no source yet is making an **initial claim**, not a transfer. Claiming GitHub performs one read-only `git ls-remote` reachability check; an empty remote is valid. It does not require a clean worktree or a published local `HEAD`, because GitHub-source deploys intentionally ship dirty and unpushed local bytes.

Changing from one GitHub repository to another follows the same lightweight contract: one reachability check, then an atomic update guarded by `sourceRevision`. The exact branch, tag, and release checks below apply only when moving authority between DeepSpace and GitHub.

**Always use the command.** Never rewrite remotes or registry fields by hand to "move" an app — authority is a server-side fact, and hand edits only desynchronize your checkout from it.

The transfer is engineered to be safe:

Inventory

The command inventories branches, tags, and deploy lineage on the current provider.

Copy or request

It copies missing objects itself, or asks you to publish the exact refs it needs. GitHub → DeepSpace is the packaged path: verified refs are imported and the `space` remote and workspace surface are enabled. DeepSpace → GitHub stays manual because you own GitHub — create or select the intended remote, publish the requested refs, then rerun.

Verify and flip

It verifies exact object ids, then flips authority **once**. A failed copy or a stale source revision leaves the old provider authoritative — there is no half-transferred state.

**`sourceRevision` counts source configuration changes, not code.** It appears in `app source --json`, `status --json`, `releases --json`, and `app list --json`, and it is a monotonic counter incremented whenever source is first claimed or changed — including a provider transfer or a switch to a different GitHub repository. It is the token that guards the update, so a concurrent change is refused rather than applied twice. Re-declaring the exact same source — the same provider and, for GitHub, the same repository — does not bump it. It is not a commit id, a release number, or any kind of code lineage: two releases from completely different trees routinely share one `sourceRevision`. Use `commitOid` (DeepSpace source) or the recorded repository (GitHub source) to identify what shipped.

Follow the command's one structured action when it returns one. Never bypass a refusal with `--no-push` — a one-release legacy escape for an unclaimed or DeepSpace-source app that skips source sync for that single deploy, shipping without commit lineage. It does not change authority and never bypasses GitHub source — moving between source modes is `app source`'s job — so using it to dodge a transfer refusal ships code the authority flip has not verified.

### What transfers, and what does not

Transfers preserve Git history reachable through **branches and tags**. They do not move:

* app identity, data, secrets, collaborators, or URLs — those key to the [app id](/guides/app-identity) and never depended on the repo host;
* Git LFS objects, submodule repositories, notes, replace refs, or host-specific metadata.

If the repo depends on LFS or submodules, resolve that before transferring — the flip will not carry them.

### Who can change it

Only the **owner** changes source authority. Collaborators and platform admins can inspect `app source` but cannot flip it.

Active DeepSpace workspaces must be **landed or dropped** before moving to GitHub — the workspace surface refuses under GitHub source, so anything still open would be stranded. See [workspaces](/guides/workspaces).

### One authority, one remote

Flipping to GitHub also **removes the `space` git remote** — the remote that points at the app's DeepSpace cloud repo. A stale one (left by an earlier `pull` on an unclaimed app, or inherited from a DeepSpace-era `deepspace clone`) still accepts `git push space` and answers with a bodiless HTTP 422, which is a confusing way to learn that DeepSpace no longer owns the repo. Flipping back to DeepSpace re-creates it.

`app source --json` reports which happened as `spaceRemote`:

| Value     | Meaning                                                                               |
| --------- | ------------------------------------------------------------------------------------- |
| `removed` | Moved to GitHub, and a `space` remote existed and was deleted                         |
| `absent`  | Moved to GitHub, and there was no `space` remote to delete                            |
| `present` | Moved to (or stayed on) DeepSpace source; the `space` remote points at the cloud repo |

Human output prints the same fact as a line under the `Source:` line.

## Working under GitHub source

The rules of the road once GitHub is authoritative:

* **Push with ordinary Git.** Branches, tags, PRs — all normal. DeepSpace Git writes and workspaces refuse with a source-authority error, by design.

* **Deploy ships the working tree.** Including dirty or unpushed bytes. Commit discipline is yours.

* **Do not create or replace `origin` unprompted.** Wiring a repo to GitHub is the developer's call. When asked, use ordinary Git:

  ```bash
  git remote add origin git@github.com:<org>/<repo>.git
  git push -u origin main
  ```

* **Publish a review branch by exact commit.** To put precisely the checked-out state up for review:

  ```bash
  git remote get-url origin
  git push -u origin HEAD:refs/heads/<review-branch>
  ```

**Never `git push --mirror`.** It acts on *every* ref — including refs that do not belong on GitHub. Push named branches and tags instead.

### The one refusal `push`, `pull`, and `clone` share

All three DeepSpace source verbs refuse identically under GitHub source, with the code `source_managed_by_github`:

> This app uses GitHub source (`owner/repo`). Use normal Git/GitHub for source operations (clone, fetch, push); `deepspace deploy` ships the local working tree without changing Git.

`--json` carries `repository` and `appId` alongside the code, so an agent can name the repo it should have used without parsing the sentence. The refusal correctly ships **no** executable action: which Git command to run is the developer's call.

### A review branch is not a second trunk

A DeepSpace-source app may still keep a review branch on GitHub — that is fine. But GitHub is then not the deploy source, and you must not describe both remotes as authoritative or maintain two trunks as a product invariant. One repository is the truth; the other holds a copy for a purpose.

### CI deploys

For CI, inspect the current flags with `npx deepspace auth login --help` and `npx deepspace deploy --help` rather than copying stale snippets. Store credentials in the CI secret manager — never in argv, repository files, or workflow logs.

## See also

* [Workspaces](/guides/workspaces) — the DeepSpace-source collaboration surface
* [App identity](/guides/app-identity) — what stays put when the repo host changes
* [Deployment](/concepts/deployment) — what a release records under each source
* [Command reference: `app source`](/cli-reference/commands#app-source)
