Skip to main content
Code Storage can keep a repository in sync with an external Git host while still exposing the Code Storage remote to your tools, automations, and users. Use Git Sync when you want to:
  • mirror a repository from GitHub, GitLab, Bitbucket, Gitea, Forgejo, Codeberg, or SourceHut
  • push to Code Storage and have those writes forwarded to the upstream provider
  • keep using Code Storage APIs, JWT-backed remotes, ephemeral branches, and webhooks on the mirrored repo

Sync modes

Code Storage currently supports three Git Sync modes:
ModeBest forAuthentication
GitHub AppPrivate GitHub repositories and webhook-driven syncGitHub App installation token
Public GitHubPublic GitHub repositories you want to pull from without credentialsNo GitHub credentials
Generic HTTPS GitGitLab, Bitbucket, Gitea, Forgejo, Codeberg, SourceHut, and similar hostsStored username/password or token

GitHub App sync

GitHub has the most direct SDK flow today. Create the repository with a GitHub base, then call pullUpstream() whenever you want to force a refresh.
const repo = await store.createRepo({
  id: 'my-synced-repo',
  baseRepo: {
    owner: 'your-github-org',
    name: 'repository-name',
    defaultBranch: 'main',
  },
  defaultBranch: 'main',
});

await repo.pullUpstream();
How it works:
  • when you create a repo with baseRepo, Code Storage links it to the specified GitHub repository
  • pullUpstream() fetches the latest changes from GitHub
  • you can then use all SDK features (diffs, commits, file access) on the synced content
  • the provider is automatically set to "github" when using baseRepo with owner and name
Code Storage treats GitHub as the source of truth for synced repositories. All pushes to Code Storage are proxied straight through to GitHub and all changes are mirrored back to Code Storage. While an initial sync is in progress, API calls against the repository return a 409 Conflict:
ApiError: repository sync in progress. please retry shortly
  status: 409,
  statusText: 'Conflict',
  method: 'GET',
  body: { error: 'repository sync in progress. please retry shortly' }
Build retry logic around this response when automating repository creation. Push webhook events are emitted once the initial sync completes.

Public GitHub mode

If the upstream repository is public, you can skip GitHub App auth and create a synced repository in public mode.
const repo = await store.createRepo({
  id: 'public-upstream-repo',
  baseRepo: {
    owner: 'octocat',
    name: 'hello-world',
    defaultBranch: 'main',
    auth: {
      authType: 'public',
    },
  },
});

await repo.pullUpstream();
Public mode is a one-time sync/import, not continuous mirroring. It is useful for templates and open-source repositories, but it is intentionally limited:
  • does not subscribe to GitHub webhooks
  • does not maintain ongoing bidirectional sync with GitHub
  • changes in GitHub are not automatically mirrored into Code Storage
  • changes in Code Storage are not automatically pushed back to GitHub
  • the GitHub repository must be publicly accessible — if it becomes private later, sync operations fail until you switch to authenticated mode
Use GitHub App mode when you need ongoing bidirectional behavior with GitHub.

Public mode vs GitHub App sync

CapabilityPublic mode (authType: public)GitHub App mode
Sync modelOne-time sync/importContinuous sync
WebhooksNot configuredConfigured and used for ongoing updates
GitHub to Code Storage updatesOne-time pull onlyAutomatically synced via webhook-driven flow
Code Storage to GitHub updatesNot automatically mirroredForwarded to GitHub
Access requirementsPublic repo only, no GitHub credentialsGitHub App installation with repository permissions

Setting up a GitHub App

To enable GitHub App sync, you need to create a GitHub App and configure it for Code Storage.
1

Create the app

Go to Settings -> Developer settings -> GitHub Apps and create a new GitHub App.
2

Set permissions

Repository permissions:
  • Contents: Read (required) or Read and write (required for full functionality)
  • Metadata: Read (required)
Read-only access lets you sync content from GitHub into Code Storage. To push changes back to GitHub — including commits, branch creation, and pull request workflows — you need Read and write on Contents.
Webhook events:
  • Push
  • Create
  • Pull Request (optional, if you want PR sync)
Webhook callback URL — either let Code Storage handle callbacks for you:
https://[your-organization].code.storage/webhooks/github
Or use your own handler.
3

Record credentials

Save these values — you will need them when configuring Code Storage:
  • GitHub App ID
  • Private Key
  • Webhook Secret

Automatic sync with webhooks

Your GitHub App can emit webhook events to automatically sync changes. You can either handle them yourself or let Code Storage manage them.

Option A: handle webhooks yourself

If you already have a webhook handler or want more control, set the webhook callback URL to point to your handler. Your handler should process GitHub events and call Code Storage as needed.
// Your webhook handler
app.post('/github-webhook', async (req, res) => {
  // Verify GitHub webhook signature
  const signature = req.headers['x-hub-signature-256'];
  if (!verifyGitHubSignature(req.body, signature, webhookSecret)) {
    return res.status(401).send('Invalid signature');
  }

  // When you receive a push event, trigger Code Storage sync
  if (req.headers['x-github-event'] === 'push') {
    const repo = await store.findOne({
      id: mapGitHubRepoToStorageId(req.body.repository.full_name),
    });

    // Manually trigger a pull from GitHub
    await repo.pullUpstream();
  }
  res.status(200).send('OK');
});
Use repo.pullUpstream() to trigger a sync from GitHub when handling events manually. For more on webhook verification and validation, see Webhooks.

Option B: let Code Storage handle webhooks

If you don’t want to run your own webhook handler:
  1. In your GitHub App settings, set the webhook URL to https://[your-organization].code.storage/webhooks/github
  2. Generate a webhook secret and save it.
  3. In the Code Storage dashboard, go to the Integrations tab, enter your webhook secret, and save.
Code Storage will handle incoming GitHub events and trigger syncs automatically.

Generic HTTPS Git sync

Generic Git Sync covers named providers that authenticate over HTTPS using a username/password pair or a token:
  • gitlab
  • bitbucket
  • gitea
  • forgejo
  • codeberg
  • sr.ht or sourcehut
For some providers, Code Storage can derive the public upstream host automatically:
  • gitlab -> gitlab.com
  • bitbucket -> bitbucket.org
  • codeberg -> codeberg.org
  • sr.ht -> git.sr.ht
For self-hosted providers, and for providers without a fixed public host like gitea and forgejo, pass upstream_host when you create the repo.

Generic Git setup

Generic provider setup happens in three steps:
  1. create the Code Storage repository with a generic Git base
  2. store the HTTPS credential for that repo
  3. trigger an initial pull

1. Create the synced repository

This example creates a repository backed by GitLab. For self-hosted instances, include upstreamHost / upstream_host.
const repo = await store.createRepo({
  id: 'my-gitlab-repo',
  baseRepo: {
    provider: 'gitlab',
    owner: 'your-group',
    name: 'your-repo',
    defaultBranch: 'main',
  },
  defaultBranch: 'main',
});
For a self-hosted provider, add upstreamHost / upstream_host:
const repo = await store.createRepo({
  id: 'my-gitea-repo',
  baseRepo: {
    provider: 'gitea',
    owner: 'your-org',
    name: 'your-repo',
    defaultBranch: 'main',
    upstreamHost: 'git.example.com',
  },
  defaultBranch: 'main',
});
The response includes the internal repo_id. Use that value in the next step.

2. Store the Git credential

Create a credential record for the repository. username is optional for providers that accept a token on its own.
await store.createGitCredential({
  repoId: repo.id,
  username: 'john_doe',
  password: 'YOUR_ACCESS_TOKEN_OR_PASSWORD',
});

3. Trigger the initial pull

await repo.pullUpstream();

How Git Sync behaves

Once a repository is configured for Git Sync:
  • git clone, git fetch, and git pull read from Code Storage
  • repo.pullUpstream() and POST /api/repos/{repo_name}/pull-upstream trigger an async refresh from the configured provider
  • git push to the default remote is forwarded to the external Git host
  • successful pushes trigger background sync so Code Storage storage nodes stay current
This lets you keep Code Storage as the stable endpoint for your app while still mirroring changes to and from the external host.

Git Sync vs Forking vs Imports

WorkflowSourceOngoing connectionBest for
Git SyncExternal Git providerYesRepositories that should stay mirrored
ForkingCode Storage repositoryNoTemplates, snapshots, isolated copies
ImportsExternal or local Git repoNoOne-time ingestion and migration
Use Git Sync when the source repository should stay connected. Use Forking when you want an independent copy. Use Imports when you want a one-time push into Code Storage.

Support

Need help integrating? Reach out directly to jacob@pierre.co.