git-gcs
Use Google Cloud Storage as a Git remote. This is a GCP equivalent of awslabs/git-remote-s3.
It provides a git remote helper that lets you use a GCS bucket as a serverless Git server — no VMs, no Secure Source Manager, just a bucket.
📦 PyPI: https://pypi.org/project/git-gcs/ 💻 GitHub: https://github.com/jwbargsten/git-gcs
This is a fork of Vicguin65/git-remote-gcs, published on PyPI as
git-gcs. The commands are still git-remote-gcs and git-gcs, and remotes still use gcs://.
Installation
macOS / Linux
pip install git-gcs
That's it. pip installs the git-remote-gcs script to a directory that's typically already on your PATH (e.g. /usr/local/bin or ~/.local/bin).
Verify it works:
which git-remote-gcs
If which returns nothing, add your Python scripts directory to your PATH. Find it with:
python3 -m site --user-base
Then add <that-path>/bin to your shell profile:
# For zsh (default on macOS)
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrc
source ~/.zshrc
# For bash
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc
Windows
pip install git-gcs
Important: Git on Windows uses its own shell and cannot find Python scripts on the normal PATH. You need to copy the executable into Git's directory. Run this from an Administrator PowerShell:
copy C:\Users\%USERNAME%\AppData\Roaming\Python\Python313\Scripts\git-remote-gcs.exe "C:\Program Files\Git\mingw64\libexec\git-core\git-remote-gcs.exe"
Note: Your Python version folder (e.g.
Python313) may differ. Find the exact path with:where.exe git-remote-gcs.exeIf that returns nothing, check
python -c "import sysconfig; print(sysconfig.get_path('scripts', 'nt_user'))"
Verify it works:
git-remote-gcs
# Should print: Usage: git-remote-gcs <remote-name> <url>
Install from source
git clone https://github.com/jwbargsten/git-gcs.git
cd git-gcs
pip install .
On Windows, also run the copy step above after installing.
Prerequisites
-
Google Cloud SDK — install from https://cloud.google.com/sdk/docs/install-sdk
-
Authentication — run once per machine:
gcloud auth application-default login
Other auth methods also work: service account keys via GOOGLE_APPLICATION_CREDENTIALS, Workload Identity (GKE), or attached service accounts (Compute Engine, Cloud Shell).
- A GCS bucket (or create one):
gcloud storage buckets create gs://my-git-bucket --location=us-west1
-
IAM permissions on the bucket. Minimum required:
storage.objects.createstorage.objects.getstorage.objects.deletestorage.objects.list
The simplest way is the Storage Object Admin role on the bucket:
gcloud storage buckets add-iam-policy-binding gs://my-git-bucket \
--member="user:you@example.com" \
--role="roles/storage.objectAdmin"
Quick Start
Create a new repo
mkdir my-repo
cd my-repo
git init
git remote add origin gcs://my-git-bucket/my-repo
echo "Hello" > hello.txt
git add -A
git commit -m "initial commit"
git branch -M main
git push --set-upstream origin main
Clone a repo
git clone gcs://my-git-bucket/my-repo my-repo-clone
Branches
cd my-repo
git checkout -b feature-branch
touch new_file.txt
git add -A
git commit -m "new feature"
git push origin feature-branch
Team Setup
To give a colleague access:
1. Grant them IAM access to the bucket:
gcloud storage buckets add-iam-policy-binding gs://my-git-bucket \
--member="user:colleague@example.com" \
--role="roles/storage.objectAdmin"
Or grant access to a Google Group:
gcloud storage buckets add-iam-policy-binding gs://my-git-bucket \
--member="group:devs@example.com" \
--role="roles/storage.objectAdmin"
For read-only access (clone and pull only), use roles/storage.objectViewer instead.
2. They install on their machine:
pip install git-gcs
gcloud auth application-default login
git clone gcs://my-git-bucket/my-repo
On Windows, they also need to run the copy step from the Windows installation section.
Access Control
Access is controlled entirely through GCS IAM. You can scope permissions per-repo using bucket prefixes and IAM conditions:
gcloud storage buckets add-iam-policy-binding gs://my-git-bucket \
--member="user:dev@example.com" \
--role="roles/storage.objectAdmin" \
--condition="expression=resource.name.startsWith('projects/_/buckets/my-git-bucket/objects/my-repo/'),title=my-repo-access"
Multiple repos can share the same bucket with different prefixes:
gcs://my-git-bucket/repo-a
gcs://my-git-bucket/repo-b
gcs://my-git-bucket/team/project-c
Data Encryption
GCS encrypts all data at rest by default with Google-managed keys. For additional control, use Customer-Managed Encryption Keys (CMEK):
gcloud storage buckets update gs://my-git-bucket \
--default-encryption-key=projects/PROJECT/locations/LOCATION/keyRings/RING/cryptoKeys/KEY
Concurrent Push Protection
git-remote-gcs uses GCS generation-match preconditions to implement per-reference locking, preventing concurrent pushes to the same branch.
If a lock acquisition fails:
error refs/heads/main "failed to acquire ref lock at my-repo/refs/heads/main/LOCK.lock.
Another client may be pushing. If this persists beyond 60s,
run git-gcs doctor gcs://my-git-bucket/my-repo --clear-locks --lock-ttl 60"
Lock age is taken from the GCS object creation time. If a push takes longer than the TTL, another client may
take over the lock; the push then fails with lock expired during upload and leaves the old bundle in place
(see git-gcs doctor). Raise the TTL for large repositories.
Configure the lock TTL via environment variable:
export GIT_REMOTE_GCS_LOCK_TTL=120 # seconds, default is 60
Managing the Remote
Doctor — diagnose and fix issues
git-gcs doctor gcs://my-git-bucket/my-repo
git-gcs doctor gcs://my-git-bucket/my-repo --delete-bundle # keep only the newest bundle per ref
git-gcs doctor gcs://my-git-bucket/my-repo --clear-locks --lock-ttl 30 # remove locks older than 30s
Protect/unprotect branches
git-gcs protect gcs://my-git-bucket/my-repo main
git-gcs unprotect gcs://my-git-bucket/my-repo main
Protected branches cannot be force-pushed to or deleted through git-remote-gcs and git-gcs.
Note: protection is a safeguard against accidents, not access control. It is a marker object that the client checks. Anyone with write access to the bucket (e.g.
roles/storage.objectAdmin) can remove the marker or overwrite bundles directly. For enforced protection, restrict write access with IAM conditions or use object holds / retention.
Delete a remote branch
git-gcs delete-branch gcs://my-git-bucket/my-repo -b old-feature
Under the Hood
How it works
Bundles are stored in GCS as <prefix>/<ref>/<sha>.bundle.
Push:
- Create a git bundle:
git bundle create <sha>.bundle <ref> - Acquire a per-ref lock using GCS generation-match preconditions
- Check protection, fast-forward and ref name conflicts against the current remote state
- Upload the bundle to
<prefix>/<ref>/<sha>.bundle - Clean up the previous bundle for that ref
- Release the lock
Fetch:
- List all objects under
<prefix>/refs/to discover refs and SHAs - Download the bundle for each requested ref
- Verify and unbundle locally with
git bundle verifyandgit bundle unbundle
List:
- Scan
<prefix>/refs/for.bundleobjects - Extract ref names and SHAs from the object keys; if a ref has several bundles, the newest one wins
- Read
<prefix>/HEADfor the default branch
Storage layout
gs://my-git-bucket/my-repo/
├── HEAD # default branch ref
├── refs/
│ ├── heads/
│ │ ├── main/
│ │ │ └── abc123...def.bundle # branch bundle
│ │ └── feature/
│ │ └── 789abc...012.bundle
│ └── tags/
│ └── v1.0/
│ └── 345def...678.bundle
Debugging
# Verbose output
GIT_REMOTE_GCS_VERBOSE=1 git push origin main
# Or use git's verbosity flag
git push -v origin main
Platform Compatibility
| macOS | Linux | Windows | |
|---|---|---|---|
pip install |
Works directly | Works directly | Requires extra copy step |
| Auth | gcloud auth application-default login |
Same | Same |
| GUI clients | Terminal-based Git only | Same | Same |
Note: GUI clients like GitHub Desktop do not support custom remote helpers. Use the command line for push/pull/clone, then open the repo in your GUI for commits, diffs, and branch management. VS Code's built-in Git panel may work directly.
Comparison with Alternatives
| Feature | git-remote-gcs | Secure Source Manager | GitHub + Cloud Build | Gitea on VM |
|---|---|---|---|---|
| Cost | ~$0 (GCS storage) | $1,000/mo | Free (public) | Free tier VM |
| Managed | Yes (GCS) | Yes | Yes | No |
| IAM integration | Native GCP IAM | Native GCP IAM | Separate | None |
| Setup time | Minutes | Minutes | Minutes | 30+ min |
| UI / PRs | No | Yes | Yes | Yes |
| Serverless | Yes | Yes | N/A | No |
Development
Requires uv and Python >= 3.12.
make sync # install dependencies
make fmt # ruff format + ruff check --fix
make lint # ruff check + ruff format --check
make test # pytest, integration tests are skipped
make dist # lint, test and build sdist + wheel
Integration tests run real git push/git clone against
fake-gcs-server (brew install fake-gcs-server):
make fake-gcs # in a separate terminal
make test-integration
Versions come from git tags (setuptools_scm). To publish a release to PyPI (needs twine credentials):
make check-dist # optional: manifest, wheel contents and metadata checks
./release.sh v0.2.0 # on a clean, pushed main: tags, builds, asks, pushes the tag, uploads
Contributing
Contributions are welcome! Please open an issue or submit a pull request on GitHub.
Inspired By
- awslabs/git-remote-s3 — the S3 equivalent this project is modeled after
- bgahagan/git-remote-s3 — the original S3 remote helper
License
Metadata
Release files for git-gcs 0.1.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| git_gcs-0.1.3.tar.gz | 84.5 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| git_gcs-0.1.3-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 103.7 kB
Release files / git_gcs-0.1.3.tar.gz
| Download URL | git_gcs-0.1.3.tar.gz |
|---|---|
| Size | 84.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
2c9b6c8e67f9bf0057d4954da988349bf9c5ffa297ca16dc09c7a7556bb1dbf4
|
|
BLAKE2b-256 checksum How to use checksums |
c5e51b951957884ddfc4d648dce0e46d4c998721ac67c7e66cd7307389910306
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.5
|
Release files / git_gcs-0.1.3-py3-none-any.whl
| Download URL | git_gcs-0.1.3-py3-none-any.whl |
|---|---|
| Size | 19.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
2d1dc490f49d8c0ecc93c3e216eb168b0963164ebdcd42ffa0d11d46fe1fa972
|
|
BLAKE2b-256 checksum How to use checksums |
8b513d280bf31395eed778d250fd70501c56f04a826fbececfd1ad00b2e5144a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.5
|
Security for git-gcs 0.1.3
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.