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

# GitHub

> Sync repositories between Mesa and GitHub (or any Git server).

Attach an upstream Git repository to a Mesa repository and trigger syncs with `syncUpstream`. Syncs report their progress through `sync.*` webhook events and are exposed on the repository's `upstream` field.

The upstream is per-repository configuration: one Mesa repo points at one upstream URL, with optional stored credentials. The upstream can be GitHub, GitLab, Bitbucket, or any Git server that supports the Git HTTPS smart protocol.

## Sync semantics

By default, Mesa's GitHub sync is conservative, and will only update branches and tags that have not diverged (fast-forward only). Deletions are not synced. This behavior is the same both when pulling upstream changes and when pushing Mesa changes to an upstream.

Mesa attempts to sync all branches and tags by default. You can also filter branches and tags for a single sync with glob patterns. If some matching branches and tags cannot be safely updated (non-fast-forward, branch protection rules, etc.), those updates will be skipped without failing the entire sync. Once a sync completes, per-ref sync outcomes are surfaced in the sync's `stats` field.

## Configure an upstream

Set an upstream when creating the repository, or attach one later with `mesa.repos.update(...)`. The `auth` payload is **write-only** — Mesa stores credentials securely and only returns the `auth_kind` on read. Repository responses include `upstream.latest_sync`, which is `null` until the first sync is enqueued.

<CodeGroup>
  ```typescript TypeScript theme={null}
  import { Mesa } from "@mesadev/sdk";

  const mesa = new Mesa({ apiKey: process.env.MESA_API_KEY });

  await mesa.repos.update({
    repo: "app",
    upstream: {
      url: "https://github.com/acme/app.git",
      auth: {
        kind: "token",
        token: process.env.GITHUB_PAT!,
        token_username: "x-access-token",
      },
    },
  });
  ```

  ```python Python theme={null}
  from mesa_sdk import Mesa
  from mesa_sdk.types import TokenAuth, UpstreamConfig

  mesa = Mesa()

  await mesa.repos.update(
      repo="app",
      upstream=UpstreamConfig(
          url="https://github.com/acme/app.git",
          auth=TokenAuth(
              token=os.environ["GITHUB_PAT"],
              token_username="x-access-token",
          ),
      ),
  )
  ```
</CodeGroup>

## Auth options

| `auth.kind`         | When to use                                                                                                                                                                                                                                                         |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `token`             | A GitHub personal access token (`github_pat_...`) or other single-token credential. `token_username` is optional; most Git servers will accept basic auth with an arbitrary username. One notable exception is BitBucket, which expects the token owner's username. |
| `username_password` | Classic username/password basic auth. Use for self-hosted Git servers or providers that do not issue tokens.                                                                                                                                                        |
| *(omitted)*         | Public upstream. Reads work without auth; pushes will fail.                                                                                                                                                                                                         |

On `repos.update(...)`, omitting a value for upstream `auth` preserves the stored credential, while passing `null`/`None` clears the stored credential:

| Value                                                       | Behavior                                                  |
| ----------------------------------------------------------- | --------------------------------------------------------- |
| Omitted                                                     | Preserve the stored credential.                           |
| `null` (TS) / `None` (Python)                               | Clear the stored credential. The upstream becomes public. |
| `{ kind: ..., ... }` / `TokenAuth` / `UsernamePasswordAuth` | Replace the stored credential.                            |

## Trigger a sync

`syncUpstream({ direction: "pull" })` fetches branches and tags from the upstream repository and applies any safe updates to the Mesa repository. `direction: "push"` works the same way in the other direction; branches and tags are fetched from your Mesa repository, and any safe updates are applied to the upstream repo.

Omit `ref_globs` or use `{ branches: "*", tags: "*" }` to sync all supported branches and tags. To sync only selected refs, pass branch and tag glob filters. If you provide `ref_globs`, include at least one of `branches` or `tags`; an omitted namespace matches no refs. Branches and tags are plain names such as `main`, `release/*`, or `v1.*`, not fully-qualified Git refs such as `refs/heads/main`.

This method enqueues a `sync` job and returns an object containing the id of the queued sync. The work is performed asynchronously. Fetch the repository and read `repo.upstream.latest_sync`, fetch the sync by id, or list the repository's upstream sync history to check status.

<CodeGroup>
  ```typescript TypeScript theme={null}
  // Pull upstream changes into Mesa
  const sync = await mesa.repos.syncUpstream({ repo: "app", direction: "pull" });
  console.log(sync.id, sync.status); // "sync_...", "queued"

  // Or pull only matching branches and tags
  await mesa.repos.syncUpstream({
    repo: "app",
    direction: "pull",
    ref_globs: {
      branches: "main",
    },
  });

  // Or push Mesa changes to the upstream
  await mesa.repos.syncUpstream({ repo: "app", direction: "push" });
  ```

  ```python Python theme={null}
  # Pull upstream changes into Mesa
  sync = await mesa.repos.sync_upstream(repo="app", direction="pull")
  print(sync.id, sync.status)  # "sync_...", "queued"

  # Or pull only matching branches and tags
  await mesa.repos.sync_upstream(
      repo="app",
      direction="pull",
      ref_globs={
          "branches": "main",
      },
  )

  # Or push Mesa changes to the upstream
  await mesa.repos.sync_upstream(repo="app", direction="push")
  ```
</CodeGroup>

<CodeGroup>
  ```typescript TypeScript theme={null}
  // See the latest sync status on the repo object
  const repo = await mesa.repos.get({ repo: "app" });
  console.log(repo.upstream?.latest_sync?.status, repo.upstream?.latest_sync?.stats);

  // Or fetch the status of a specific sync
  const sync = await mesa.repos.getUpstreamSync({ repo: "app", syncId: "sync_..." });
  console.log(sync.status, sync.stats);

  // Or list the sync history for a repo
  const { syncs } = await mesa.repos.listUpstreamSyncs({ repo: "app", limit: 20 });
  for (const sync of syncs) {
    console.log(sync.direction, sync.status, sync.created_at);
  }
  ```

  ```python Python theme={null}
  # See the latest sync status on the repo object
  repo = await mesa.repos.get(repo="app")
  print(repo.upstream.latest_sync.status if repo.upstream and repo.upstream.latest_sync else None)

  # Or fetch the status of a specific sync
  sync = await mesa.repos.get_upstream_sync(repo="app", sync_id="sync_...")
  print(sync.status, sync.stats)

  # Or list the sync history for a repo
  result = await mesa.repos.list_upstream_syncs(repo="app", limit=20)
  for sync in result.syncs:
      print(sync.direction, sync.status, sync.created_at)
  ```
</CodeGroup>

`status` transitions through `queued` → `in_progress` → `completed` or `failed`.

Once a sync completes, a `stats` field becomes available with per-ref outcomes (`updated`, `unchanged`, `filtered`, or `rejected`). `stats.refs` includes only matching source branches and tags.

If a sync fails, the `error` field provides a failure reason.

## Subscribe to sync events

Syncs emit four webhook event types over their lifecycle. Subscribe on a webhook target to drive your own workflows without polling.

| Event              | Fires when                                                                     |
| ------------------ | ------------------------------------------------------------------------------ |
| `sync.queued`      | A `syncUpstream` call enqueues a new sync.                                     |
| `sync.in_progress` | The worker picks up the sync.                                                  |
| `sync.completed`   | The sync finishes with terminal status `completed`.                            |
| `sync.failed`      | The sync finishes with terminal status `failed`. The payload includes `error`. |

```ts theme={null}
const target = await mesa.webhookTargets.create({
  url: "https://acme.dev/webhooks/mesa",
  events: ["sync.completed", "sync.failed"],
  repo_ids: ["repo_abc123"],
});
```

Pull syncs also cause the `repo.push` webhook event to fire if upstream changes are received by Mesa.

See [Webhooks](/content/concepts/webhooks) for delivery semantics, signature verification, and the full event payload shape.

## Required scopes

| Operation                        | Scope                     |
| -------------------------------- | ------------------------- |
| Attach or update upstream config | `write` on the repository |
| Sync upstream                    | `write` on the repository |
| Read sync history                | `read` on the repository  |

## REST endpoints

Each SDK method maps to a REST endpoint under the organization root:

| Method              | Endpoint                                         |
| ------------------- | ------------------------------------------------ |
| Configure upstream  | `POST /v1/:org/repos` and `PATCH /v1/:org/:repo` |
| Sync upstream       | `POST /v1/:org/:repo/upstream/syncs`             |
| Get upstream sync   | `GET /v1/:org/:repo/upstream/syncs/:syncId`      |
| List upstream syncs | `GET /v1/:org/:repo/upstream/syncs`              |

See the [REST API reference](/content/api-reference/overview) for full request and response schemas.
