Metadata-Version: 2.4
Name: git-remote-gcs
Version: 0.1.0
Summary: A git remote helper for Google Cloud Storage
Author-email: Tyler <tyler@zerosumdefense.co>
License-Expression: Apache-2.0
Classifier: Topic :: Software Development :: Version Control
Classifier: Topic :: Software Development :: Version Control :: Git
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: google-cloud-storage<3.0.0,>=2.14.0

# git-remote-gcs

Use Google Cloud Storage as a Git remote. This is a GCP equivalent of [awslabs/git-remote-s3](https://github.com/awslabs/git-remote-s3).

It provides a [git remote helper](https://git-scm.com/docs/gitremote-helpers) that lets you use a GCS bucket as a serverless Git server — no VMs, no Secure Source Manager, just a bucket.

## Installation

```bash
pip install git-remote-gcs
```

Or install from source:

```bash
git clone <this-repo>
cd git-remote-gcs
pip install .
```

## Prerequisites

1. A GCP project with a GCS bucket (or create one):

```bash
gcloud storage buckets create gs://my-git-bucket --location=us-west1
```

2. Authentication configured — any of:
   - `gcloud auth application-default login` (local dev)
   - Service account key via `GOOGLE_APPLICATION_CREDENTIALS`
   - Workload Identity (GKE)
   - Attached service account (Compute Engine, Cloud Shell)

3. IAM permissions on the bucket. Minimum required:
   - `storage.objects.create`
   - `storage.objects.get`
   - `storage.objects.delete`
   - `storage.objects.list`

   The simplest way is the **Storage Object Admin** role (`roles/storage.objectAdmin`) on the bucket:

```bash
gcloud storage buckets add-iam-policy-binding gs://my-git-bucket \
  --member="user:tyler@zerosumdefense.co" \
  --role="roles/storage.objectAdmin"
```

   For more granular per-repo access using prefixes, see [Access Control](#access-control).

## Quick Start

### Create a new repo

```bash
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 push --set-upstream origin main
```

### Clone a repo

```bash
git clone gcs://my-git-bucket/my-repo my-repo-clone
```

### Branches

```bash
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
```

## Access Control

Access is controlled entirely through GCS IAM. You can scope permissions per-repo by using bucket prefixes and IAM conditions:

```bash
# Grant access to a specific repo prefix only
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)](https://cloud.google.com/storage/docs/encryption/customer-managed-keys):

```bash
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](https://cloud.google.com/storage/docs/request-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 --lock-ttl 60 to inspect and clear stale locks."
```

Configure the lock TTL via environment variable:

```bash
export GIT_REMOTE_GCS_LOCK_TTL=120  # seconds, default is 60
```

## Managing the Remote

### Doctor — diagnose and fix issues

```bash
git-gcs doctor gcs://my-git-bucket/my-repo
git-gcs doctor gcs://my-git-bucket/my-repo --delete-bundle  # remove conflicting bundles
git-gcs doctor gcs://my-git-bucket/my-repo --lock-ttl 30    # clear locks older than 30s
```

### Protect/unprotect branches

```bash
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.

### Delete a remote branch

```bash
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:**
1. Acquire a per-ref lock using GCS generation-match preconditions
2. Create a git bundle: `git bundle create <sha>.bundle <ref>`
3. Upload the bundle to `<prefix>/<ref>/<sha>.bundle`
4. Clean up the previous bundle for that ref
5. Release the lock

**Fetch:**
1. List all objects under `<prefix>/refs/` to discover refs and SHAs
2. Download the bundle for each requested ref
3. Unbundle locally with `git bundle unbundle`

**List:**
1. Scan `<prefix>/refs/` for `.bundle` objects
2. Extract ref names and SHAs from the object keys
3. Read `<prefix>/HEAD` for 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

```bash
# Verbose output
GIT_REMOTE_GCS_VERBOSE=1 git push origin main

# Or use git's verbosity flag
git -c transfer.verbosity=2 push origin main
```

## 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 |

## Inspired By

- [awslabs/git-remote-s3](https://github.com/awslabs/git-remote-s3) — the S3 equivalent this project is modeled after
- [bgahagan/git-remote-s3](https://github.com/bgahagan/git-remote-s3) — the original S3 remote helper

## License

Apache-2.0
