Cloud Source Repositories is Google Cloud's original hosted Git service: private repositories that live inside a project, controlled by IAM, wired to Cloud Build triggers and Pub/Sub notifications, and able to mirror a GitHub or Bitbucket repository. Read its current status first. Google's documentation says that effective June 17, 2024, Cloud Source Repositories is not available to new customers. Organisations that used it before that date keep access, but a project in an organisation that never used it cannot enable the API. Google states that a shutdown date will be announced at least one year before shutdown, and recommends Secure Source Manager, a regionally deployed, single-tenant managed repository service, or a third-party Git host.

This article is therefore written for the team that already runs on Cloud Source Repositories. It explains how the service fits together, how to operate it safely while you still depend on it, how to inventory every repository, trigger and notification, and how to migrate with history, access and automation intact. If you are starting fresh, skip to the migration section to see what you would be building on instead. Commands and payloads here were checked against Google's current documentation; where the documentation is silent, such as published size limits, the article says so rather than guessing.

How the service fits together

A repository belongs to a project and is addressed by project ID and repository name. Clones use a URL of the form https://source.developers.google.com/p/PROJECT_ID/r/REPOSITORY_NAME. Authentication works three ways: through the gcloud CLI after gcloud init, which the gcloud source repos clone command uses to configure credentials for you; through SSH public keys registered with the service (RSA of at least 2048 bits, ECDSA or ED25519); or through manually generated credentials from the service's Configure Git page, for machines without gcloud.

Around each repository sit four integrations, and they are what makes migration more than a git push. Mirroring keeps a read-only copy of a GitHub or Bitbucket repository in sync. Pub/Sub notifications publish an event for every push, repository creation and deletion. Cloud Build triggers start builds when matching branches or tags change. IAM decides who reads, writes and administers. A repository is rarely referenced by name alone: build configs, deployment scripts, Cloud Functions source settings and developer remotes all contain its URL.

Cloud Source Repositories in an existing project, and where each link goes after migrationDevelopersgit over HTTPS / SSHGitHub / Bitbucketcloud-hosted originCloud Source Repositoriesproject-scoped Git repospush / clonemirror (read-only)Pub/Sub topicRefUpdate / CreateRepo / DeleteRepoCloud Build triggerbranch / tag regexIAMroles/source.reader|writer|adminMigration target for each linkGit datamirror push to new hostAccessre-grant on new hostBuild triggersconnect SCM or webhookNotificationsnew host's eventsSecure Source Manager is Google's recommended replacement; a third-party Git host also works
Cloud Source Repositories with its four integrations, and the replacement for each when the repository moves.

Access control and PushBlock

Access is plain IAM. Three predefined roles cover the service, and the binding scope matters:

RoleGrantsBind at
roles/source.readerList, clone, fetch and browse repositoriesRepository or project
roles/source.writerReader permissions plus updating (pushing to) repositoriesRepository or project
roles/source.adminCreate, update, delete, list, clone, fetch, browse; read and change IAM policiesProject only, because the create permission applies only at project scope

Prefer repository-level bindings for writers, so a CI service account that pushes generated code to one repository cannot push to all of them. Project-level reader is reasonable for internal visibility. Treat roles/source.admin like any role that can change IAM: few humans, no automation. Groups are better than individual principals, because a migration has to re-create every grant on the new host, and a short list of groups is far easier to map than hundreds of user bindings. For the wider IAM model, see GCP IAM: roles, principals and the resource hierarchy.

One project-level safety control is worth enabling everywhere: PushBlock. Running gcloud source project-configs update --enable-pushblock makes the service reject git pushes that contain private key data in every repository in the project. It is a coarse guard, not a secret scanner, but it stops the most damaging accident. Credentials belong in a secret store; see GCP Secret Manager for how to keep them out of repositories entirely.

Push notifications through Pub/Sub

Notifications let other systems react to pushes without polling. You attach a Pub/Sub topic to one repository, or to the project so that all repositories publish, and choose a message format of JSON or protocol buffers. Publishing uses a service account you name, and the caller needs permission to update the repository or project configuration and to act as that service account.

gcloud pubsub topics create source-events
gcloud source repos update payments-api \
    --add-topic=source-events \
    --message-format=json \
    --service-account=csr-publisher@my-project.iam.gserviceaccount.com

Three event types exist: CreateRepo, RefUpdate for every git push, and DeleteRepo. A push event carries the pusher's email and one entry per updated ref, each with an update type of CREATE, UPDATE_FAST_FORWARD, UPDATE_NON_FAST_FORWARD or DELETE and the old and new commit IDs. A non-fast-forward update to a protected branch means someone force-pushed and rewrote history, which is exactly the event worth alerting on. This subscriber does that:

import json
from google.cloud import pubsub_v1

PROTECTED = {"refs/heads/main", "refs/heads/release"}
sub = pubsub_v1.SubscriberClient()
path = sub.subscription_path("my-project", "source-events-audit")

def handle(message):
    event = json.loads(message.data)
    update = event.get("refUpdateEvent")
    if update:
        for ref, change in update.get("refUpdates", {}).items():
            risky = change["updateType"] in ("UPDATE_NON_FAST_FORWARD", "DELETE")
            if ref in PROTECTED and risky:
                alert(repo=event["name"], ref=ref, who=update.get("email"),
                      old=change.get("oldId"), new=change.get("newId"))
    message.ack()

future = sub.subscribe(path, callback=handle)
future.result()   # block; run under a supervisor in production

Keep handlers idempotent and acknowledge only after processing, because Pub/Sub delivers at least once. Record the old commit ID from every non-fast-forward event: it is the pointer you need to restore the branch, and after a force-push it may be the only easy record of where the branch used to be.

Mirrors and build triggers

Mirroring copies a GitHub or Bitbucket repository into Cloud Source Repositories and keeps it synced automatically when commits are pushed to the origin. Only the cloud-hosted versions of GitHub and Bitbucket are supported; self-hosted GitHub Enterprise Server and Bitbucket Server are not. The mirror is read-only: you never push to it, and the origin remains the source of truth. Teams typically used mirrors so that Cloud Build triggers, Cloud Functions source deployments or debugging tools could read code without a direct integration with the external host.

That history matters during migration. A mirrored repository needs no Git data migration at all, because the origin already has everything. What needs migrating is whatever consumes the mirror: point those consumers at the origin through a direct connection, then delete the mirror.

Builds connect the same way. A trigger on a Cloud Source Repositories repository is created with gcloud builds triggers create cloud-source-repositories, naming the repository, a branch or tag regular expression in RE2 syntax, and a build config path in the repository:

gcloud builds triggers create cloud-source-repositories \
    --name=payments-api-main \
    --repo=payments-api \
    --branch-pattern='^main$' \
    --build-config=cloudbuild.yaml \
    --included-files='src/**,cloudbuild.yaml' \
    --service-account=projects/my-project/serviceAccounts/builder@my-project.iam.gserviceaccount.com

List every trigger before migrating, because each one names the repository and must be re-created against the new host. For the build side of this pipeline, see GCP Cloud Build: managed CI/CD.

Inventory before you migrate

Migration starts with an inventory, because repositories hide in projects nobody remembers. Google's planning steps are to find every repository with the project selector, confirm you hold Project Owner or Source Repository Administrator, decide which repositories are still active and connected to builds, and delete the unused ones. Script the first step across the organisation:

#!/usr/bin/env bash
# Inventory repositories, mirrors and triggers in every project you can see.
for p in $(gcloud projects list --format="value(projectId)"); do
  repos=$(gcloud source repos list --project="$p" \
          --format="csv[no-heading](name,mirrorConfig.url)" 2>/dev/null) || continue
  [ -z "$repos" ] && continue
  echo "== $p"
  echo "$repos"
  gcloud builds triggers list --project="$p" \
      --format="table(name,triggerTemplate.repoName,triggerTemplate.branchName)" 2>/dev/null
done

Projects where the API is disabled return an error that the script skips. The trigger listing covers global triggers only; repeat it with --region for each region where you run Cloud Build. The output gives, per project, each repository, whether it is a mirror, and the triggers that reference it. Add three more columns by hand or by search: Pub/Sub topics attached to repositories or the project, IAM bindings at repository and project level, and code or configuration that embeds the clone URL. A repository-wide search for the host name source.developers.google.com across your other repositories and deployment configs catches the last group.

Migrating repositories, access and automation

For each active repository that is not a mirror, the Git data moves with a mirror clone and a mirror push, which carries all branches, tags and history. Branch protection rules, repository settings and access control do not travel with the Git data and must be re-created on the destination.

git clone --mirror https://source.developers.google.com/p/my-project/r/payments-api
cd payments-api.git
git remote add ssm SSM_REPOSITORY_URL
git push --mirror ssm

# verify: compare these outputs from fresh clones of both sides
git branch -r
git tag
git rev-parse HEAD
git rev-list --all --count

Access maps role by role. Google's mapping to Secure Source Manager is the following, and it shows one structural difference: Secure Source Manager has instances, so every reader and writer also needs access to the instance.

Cloud Source RepositoriesSecure Source Manager
roles/source.readersecuresourcemanager.repoReader + securesourcemanager.instanceAccessor
roles/source.writersecuresourcemanager.repoWriter + securesourcemanager.instanceAccessor
roles/source.adminsecuresourcemanager.instanceAccessor, repoAdmin, instanceRepoCreator, repoCreator (or securesourcemanager.admin)

Automation moves by use case. Google's guidance is: for builds, connect Cloud Build to the new source code management system or use webhook triggers; for infrastructure-as-code artefacts, Artifact Registry OCI or Helm repositories, Secure Source Manager or another Git host; for private network connectivity, Developer Connect, Service Directory or Cloud Build repositories; for backups, a third-party backup product. Replace Pub/Sub consumers with the new host's own event mechanism, and keep the alerting logic from the subscriber above.

Worked example: a zero-loss cut-over

Take a team with one repository, payments-api, one trigger that builds main and deploys to Cloud Run, a Pub/Sub topic feeding the force-push alert, and twelve developers in a writers group. A cut-over that avoids lost commits runs in this order:

  1. Create the destination repository, grant the mapped roles to the same groups, and re-create branch protection.
  2. Mirror-push once to seed it, verify counts and HEADs, and create the new build trigger in a disabled or approval-required state.
  3. Announce a freeze window and revoke write access at both repository and project scope, including roles/source.writer granted on the project; then have a former writer attempt a push and confirm it is rejected.
  4. Mirror-push again to capture the final commits, and re-run the four verification commands against both sides.
  5. Enable the new trigger, push a no-op commit and confirm the build deploys; disable the old trigger.
  6. Developers run git remote set-url origin NEW_URL; update scripts and configs found by the URL search.
  7. Keep the old repository read-only for a defined period, then delete it and its topic attachment.

Revoking write access, and proving it with a rejected test push, before the final push is the step teams skip; skipping it is how a commit pushed during the window ends up only on the old host. The whole sequence takes an afternoon for one repository; for fifty, script steps two to four and run them in batches. The deployment side, if it targets Cloud Run, is covered in Cloud Run, in depth.

Failure modes

  • Enabling the API in a new project fails. That is the end-of-sale policy working as designed for organisations without prior use; do not build new workflows that assume it.
  • Commits lost at cut-over. Someone pushed to the old repository after the final mirror push. Revoke write access first, then push.
  • Builds silently stop. A trigger still points at the old repository, which no longer changes. Compare trigger lists before and after, and alert on build absence, not only build failure.
  • Access denied after migration. Readers were granted repository roles but not instance access on Secure Source Manager. Use the mapping table, including the accessor role.
  • Mirror deleted while still consumed. A Cloud Function or trigger read from the mirror. Inventory consumers before deleting.
  • Duplicate alerts from notifications. At-least-once delivery without idempotent handlers. Key alerts on repository, ref and new commit ID.

Trade-offs

OptionStrengthsCosts
Stay on Cloud Source Repositories for nowNo work today; existing integrations keep runningNo new customers means a shrinking product; a shutdown notice will force the move later on a deadline
Secure Source ManagerGoogle's recommended path; IAM-native; regional, single-tenant instancesInstance model to learn and pay for; triggers and notifications must be rebuilt
Third-party Git hostLarge ecosystem, reviews and CI built inIdentity and access outside Google Cloud IAM; network and build connectivity to set up

The decision is rarely about Git itself, which is identical everywhere. It is about where identity, review workflow and build triggers live. Choose the host your build and review tools already integrate with, and migrate before the shutdown notice turns a planned project into a deadline.

What to do next

  1. Run the inventory script across your organisation and list repositories, mirrors and triggers per project.
  2. Enable PushBlock on every project that still hosts repositories.
  3. Search code and deployment configs for source.developers.google.com and record every reference.
  4. Delete unused repositories and mirrors whose consumers you have repointed.
  5. Pick the destination host and map each IAM binding, adding instance access if you choose Secure Source Manager.
  6. Migrate one low-risk repository with the freeze, final push and verification sequence; time it.
  7. Rebuild triggers and push alerts on the new host, then retire the old topic attachments.
  8. Schedule the remaining repositories in batches, well ahead of any shutdown announcement.
Key takeaway: Cloud Source Repositories has not been available to new customers since June 17, 2024, and Google will give at least a year's notice before shutting it down. Teams still on it should inventory repositories, mirrors, triggers, topics and IAM bindings, enable PushBlock, and migrate one repository at a time with a write freeze, a final mirror push and verification, re-creating access and automation on Secure Source Manager or another Git host.