Skip to content
467 linesCodeBlameRaw
1---
2title: Git
3description: Remotes, credentials, private repositories, deploy keys and limits.
4---
5
6g1t speaks git's smart HTTP protocol. Any git client works.
7
8## Remotes
9
10```text
11https://g1t.sh/<workspace>/<repo>.git
12```
13
14Public repositories can be cloned without signing in:
15
16```sh
17git clone https://g1t.sh/flagon-io/g1t.git
18```
19
20If the workspace is [renamed](/guides/workspaces/#rename-a-workspace), the
21old remote redirects to the new one for 90 days. Git follows the redirect
22and warns about it; point the remote at the new address:
23
24```sh
25git remote set-url origin https://g1t.sh/<new-workspace>/<repo>.git
26```
27
28## Authentication
29
30Pushing, and reading private repositories, needs credentials. Use your
31username, and as the password either your account password or an
32[access token](https://g1t.sh/settings/tokens). Tokens are recommended: they can be revoked
33individually and they also work for the API.
34
35To avoid typing it each time, let git store it:
36
37```sh
38git config --global credential.helper store
39```
40
41## Creating a repository by pushing
42
43Pushing to a repository that does not exist, in a workspace you belong to,
44creates it as a private repository, so nothing pushed by mistake is
45published. To make it public, see
46[change who can see a repository](/guides/managing-repositories/#change-who-can-see-a-repository).
47
48```sh
49git push https://g1t.sh/<workspace>/new-repo.git main
50```
51
52## Private repositories
53
54A private repository is visible only to people with a
55[role](/guides/access-and-roles/) on it. Cloning and fetching need
56Read, and pushing needs Write. To
57everyone else it looks exactly like a repository that does not exist, both
58on the site and to git.
59
60On the site, an address you cannot see gives the same page either way, with
61status 404:
62
63| You are | The page says |
64| --- | --- |
65| Signed out | **Nothing here**: this page doesn't exist, or it's private; sign in if it's yours. **Sign in** brings you back to the same address. |
66| Signed in | **Nothing here**: this page doesn't exist, or you don't have access to it, with which account you are signed in as and a link to switch account. If you should have access, ask someone with the Admin role on it to add you. |
67
68Issues, pull requests and workspace pages work the same way. The sidebar
69does not open the project or workspace the address names, so nothing on
70the page hints at whether it exists; a missing file, commit or issue in a
71project you can see keeps that project's sidebar. A profile that does not
72exist says **No one on g1t goes by that name**, since profiles are public.
73
74## Download a ZIP
75
76On a repository's **Files** page, **Code** → **Download ZIP** downloads the
77branch shown as one zip, its files in a folder named `<repo>-<branch>`. It
78works for anyone who can see the repository, and for any branch, tag or
79commit at `g1t.sh/<workspace>/<repo>/archive/<ref>.zip`. A ZIP holds the
80files, not the history; clone for that. A repository with more than 10,000
81files or over 24 MB is too large to download this way, so clone it instead.
82
83## Raw files
84
85**Raw**, above a file on its page, opens the file as it is, with nothing
86around it. Any file is at:
87
88```text
89https://g1t.sh/<workspace>/<repo>/raw/<branch, tag or commit>/<path>
90```
91
92That address sends you on to the commit the branch or tag names now, on
93g1t's file host:
94
95```text
96https://g1tusercontent.com/<workspace>/<repo>/raw/<commit>/<path>
97```
98
99g1tusercontent.com is a site of its own so that nothing in a repository
100can reach your g1t.sh session: it never receives g1t.sh's cookies, and a
101file opened there cannot run script. Images, video, audio and PDFs are
102served as themselves; any other text, HTML, SVG source, XML and
103JavaScript included, as plain text; anything else as a download. A file
104is served up to 10 MB; clone the repository for larger ones.
105
106- **Public repositories.** The address works for anyone, and an address
107 at a commit can be kept for good.
108- **Private repositories.** The address carries a `token` that g1t.sh
109 makes for someone who can read the repository. It is good for that one
110 file for an hour or two; after that, open the file on g1t.sh again for a
111 new address. Revoking someone's access does not end an address they
112 already have before then.
113
114Images in a file's page show from its raw address, and so do pictures in a
115README that name a file in the repository (`![Diagram](docs/diagram.png)`,
116relative to the README's folder, or `/docs/diagram.png` from the
117repository's root): they show the file at the commit the page shows.
118Uploaded avatars are on the same host, at
119`https://g1tusercontent.com/avatars/<sha256>`.
120
121## Browsing without an account
122
123Public projects, Explore, Search and profiles are open to everyone, in the
124same sidebar members use. Signed out, the sidebar has Explore and Search,
125and in a project its Code, Issues, Pull requests, Agents, Workflows and
126Deployments; pages only people with a role on the repository see, such as
127Security and Settings, are left out. **Sign in** and **Sign up** sit at the bottom, and both bring you
128back to the page you were on.
129
130## Protected branches
131
132A repository protects its branches and tags with [rulesets](/guides/rules/),
133under **Settings → Rules**. A push that breaks a rule is refused for
134everyone, whatever their role, and for agents, unless a ruleset lists them
135as able to bypass it. Git prints which ruleset and rule refused it, and how
136to fix it:
137
138```text
139remote: error: rules for refs/heads/main declined this push:
140remote: - Changes to main must be made through a pull request. [ruleset "Protect main", pull_request]
141remote: Push a branch, open a pull request into main, and merge it.
142 ! [remote rejected] main -> main (declined by ruleset "Protect main" (pull_request))
143```
144
145With **Require a pull request before merging**, changes reach a branch only
146by merging a pull request. Creating the branch, such as the first push to
147an empty repository, is still allowed. Rulesets also block force pushes and
148deletions, restrict who creates branches and tags, check commit messages,
149signatures and the files a push changes, and set what a merge needs. See
150[rules](/guides/rules/).
151
152## Branches
153
154Push any branch to a repository you can write to, and open a
155[pull request](/concepts/overview/#pull-requests) from it on the
156repository's **Pull requests** tab.
157
158```sh
159git switch -c my-change
160git push origin my-change
161```
162
163A repository's **Branches** tab, `g1t.sh/<workspace>/<repo>/branches`, lists
164every branch: the default one first, then those with a commit in the last 90
165days (**Active**), then the rest (**Stale**). Each shows its last commit, how
166many commits it is ahead of and behind the default branch, the pull request
167open on it with its checks, and its preview when it has one. A branch with
168no pull request links to opening one; one with nothing the default branch
169lacks (ahead `0`) says **Nothing to merge** instead. The counts are exact,
170merges included. g1t reads up to 1,000 commits of each history to find
171where the two meet; a branch that left the default branch further back
172than that shows no counts. Search narrows the list by name.
173
174The **Tags** tab lists tags newest first, up to 100, each with its commit
175and a ZIP of its files.
176
177**Compare**, `g1t.sh/<workspace>/<repo>/compare/<base>...<head>`, shows
178what one branch has that another does not: its commits, then every change.
179Pick the two branches at the top; **Open a pull request** starts one from
180the compared branch.
181
182On **Files**, each file and folder shows the commit that last changed it and
183when, from the branch's whole history. On a long history the first view
184can show some of them blank while g1t finishes reading it; a later view
185fills them in, and after a push only the new commits are read. The branch menu at the top switches branch and keeps the
186folder or file you are on.
187
188## Pull request forks
189
190A pull request that was not opened from a branch has its own remote:
191
192```text
193https://g1t.sh/pulls/<pull request id>.git
194```
195
196Only whoever opened the pull request can push to it, or, for one g1t
197made, whoever asked for it. Pushes to a fork
198update the pull request's head commit on its page.
199
200## Limits
201
202### Size limits
203
204Repositories are stored in Cloudflare Artifacts. g1t checks its limits
205before a push is stored, and declines a push that would cross one. git
206prints the reason beside each branch (`! [remote rejected] main (…)`), and
207what to do as `remote:` lines. Nothing in a declined push is stored.
208
209| Limit | Size | What happens past it |
210| --- | --- | --- |
211| A file | 32 MB | The push is declined, naming the file's size. |
212| A repository, with its pull requests' forks | 950 MB, as g1t counts what was pushed (the store holds 1 GB) | The push is declined; once full, pushes are refused with the reason before any data is sent. |
213| A push that push protection can scan before it lands | Most pushes; very large ones are scanned after they land | A very large push goes through and is scanned after it lands; secrets found are open alerts. To have it checked first, push in parts, oldest commits first. |
214| A push | 100 MB | Refused by the network with HTTP `413` before g1t sees it. |
215
216To push a large history in parts:
217
218```sh
219git rev-list --reverse HEAD | awk 'NR % 500 == 0' | xargs -I{} git push origin {}:refs/heads/main
220git push origin main
221```
222
223Each push sends only what the one before did not.
224
225### Pushes of many branches or tags
226
227Every branch and tag in a push is stored. Each one is also announced as a
228`git.push` event, which starts workflows, mirrors the repository and
229calls webhooks, except in a push of many:
230
231| A push of | What is announced |
232| --- | --- |
233| Up to 3 tags | Each tag |
234| More than 3 tags (`git push --tags`, say) | None of the tags |
235| Up to 1,000 branches | Each branch |
236| More than 1,000 branches | Only the default branch, if it moved |
237
238To have tags start workflows, push them 3 or fewer at a time.
239
240### When the store is busy
241
242If Cloudflare Artifacts is rate limiting g1t or not answering, g1t tries
243reads again for a moment, then answers git with HTTP `429` (rate limited)
244or `503` (unavailable) and a `Retry-After` header saying how many seconds
245to wait. Pushes are never tried again on your behalf: run `git push`
246again. On g1t.sh the page says the git storage is busy instead of failing,
247and [status.g1t.sh](https://status.g1t.sh) shows **Git storage**.
248
249### Git operations
250
251Each clone, fetch and push is a git operation, your agents' included:
252their sandboxes use the same git endpoints you do, and a pull request's
253working copy counts for its repository's workspace. Every workspace has 50,000
254a month included. Past that, a workspace on the g1t plan pays $0.18 per
2551,000, and a free workspace is never charged: past 50,000 in a month, its
256git requests past 60 in an hour are answered `429` with when to try again,
257until the month turns. Counting starts on 2026-10-14. See
258[git operations](/guides/usage-and-billing/#git-operations).
259
260### Request limits
261
262Git requests without credentials are limited to 120 a minute from each IP
263address, about 40 clones; with credentials, 1,200 a minute for each set of
264credentials. Anonymous clones of one repository that g1t has not cached
265are limited to 120 a minute, whoever makes them. Past a limit, git is
266answered `429` with a message saying to wait a minute. Clone with
267[credentials](#authentication) to count against your own limit. See
268[rate limits](/reference/rate-limits/).
269
270What these limits mean in practice, and what to do instead, is on
271[What g1t can't do yet](/about/limitations/#git).
272
273## Where a slow request's time went
274
275Every answer g1t gives git carries a `Server-Timing` header: how many
276milliseconds each step of the request took. To see it, run git with its
277HTTP trace on:
278
279```sh
280GIT_TRACE_CURL=1 git ls-remote https://g1t.sh/<owner>/<repo>.git 2>&1 | grep -i server-timing
281```
282
283| Step | What it is |
284| --- | --- |
285| `repo` | Finding the repository, and checking your credentials if you sent any |
286| `moved` | Only for an address with no repository: looking for a renamed workspace or a transferred repository to send you to |
287| `access` | Deciding whether you may fetch from or push to it |
288| `kept` | A free workspace's limits, and looking for a ref listing and a store credential made a moment ago |
289| `mint` | Only when no credential was kept: the git store making one for the request |
290| `store` | The git store's answer; for a push, checking it for secrets first |
291| `refs` | Only for a push: recording that the repository's refs changed |
292| `total` | Everything g1t did |
293| `repos` | The same, measured where your request arrived |
294
295Two entries say how a step went rather than how long it took:
296
297| Entry | Values |
298| --- | --- |
299| `refs;desc=` | `hit-colo` or `hit-shared` when the ref listing came from g1t's cache, `miss` when the git store was asked |
300| `pack;desc=` | Only for a fresh clone: `hit` when its pack came from g1t's cache, `miss` when the git store built it |
301| `cred;desc=` | `isolate` or `shared` for a store credential made a moment ago, `mint` for a new one |
302
303The ref listing git asks for first on every clone and fetch is kept for up
304to a minute, and only the same question about the same refs gets the same
305answer: a push, a merge or any other change to a repository's branches and
306tags makes the next fetch ask the git store again. A change can take up to
3075 seconds to reach every fetch.
308
309A fresh clone, one that has no objects yet (shallow clones such as
310`git clone --depth=1` included), has its pack kept too, for up to 7 days
311or until the repository's branches or tags next change. The next clone that
312asks for the same commits in the same way gets the same pack without the
313git store building it again. A fetch into a repository you already have,
314and any pack over 200 MB, always goes to the git store.
315
316Include the header when you report a slow clone, fetch or push.
317
318## SSH
319
320Git over SSH is not available yet. Use HTTPS, which works for clone,
321fetch and push everywhere SSH would.
322
323Why: git over SSH needs raw TCP connections on port 22, and g1t runs
324entirely on Cloudflare's network. Accepting inbound TCP traffic directly
325into Workers is in a beta from Cloudflare that g1t has applied for and is
326waiting on. SSH keys can already be added under
327[Settings → SSH keys](https://g1t.sh/settings/keys),
328and will be used once SSH is on. So can [deploy keys](#deploy-keys).
329
330## Deploy keys
331
332A deploy key is an SSH key that reaches one repository and nothing else.
333Give one to a server or a pipeline that needs to clone a repository, or
334push to it, without a person's account behind it. A deploy key belongs to
335the repository: it keeps working when the person who added it leaves the
336workspace, and it is not tied to anyone's role.
337
338:::note
339Deploy keys are used over SSH, which is [not on yet](#ssh). You can add
340them now, and they will work as soon as SSH is. Until then, a machine can
341clone and push over HTTPS with a
342[workspace access token](/guides/workspaces/#workspace-access-tokens).
343:::
344
345### Read-only or read and write
346
347A deploy key is read-only unless you choose **Allow write access** when
348you add it:
349
350| Access | It can |
351| --- | --- |
352| **Read-only** (the default) | Clone and fetch the repository, private or not. |
353| **Read and write** | Clone, fetch and push, workflow files under `.g1t/workflows/` and `.github/workflows/` included. |
354
355Either way it reaches only its own repository: any other address is
356refused, in its workspace or anywhere else, and so is pushing to an
357address with no repository, which would otherwise make one.
358
359A key with write access can change workflows, and workflows run with the
360repository's secrets. Allow it only for a machine that must push. You
361cannot change a key's access later: delete it and add it again.
362
363### Add a deploy key
364
365You need the Admin role on the repository and a confirmed email address.
366
3671. Make a key pair on the machine that will use it, without a passphrase
368 if it runs unattended:
369
370 ```sh
371 ssh-keygen -t ed25519 -C "deploy@build-server" -f ~/.ssh/g1t_deploy -N ""
372 ```
373
3742. Open the repository's **Settings → Deploy keys**,
375 `g1t.sh/<workspace>/<repo>/settings/keys`.
3763. Under **Add a deploy key**, give it a **Title**, such as the machine that
377 uses it, and paste the public key (`~/.ssh/g1t_deploy.pub`) into **Key**.
378 Left without a title, it takes the key's comment.
3794. Choose **Allow write access** only if the machine must push.
3805. Choose **Add deploy key**.
381
382g1t takes `ssh-ed25519`, `ecdsa-sha2-nistp256`, `ecdsa-sha2-nistp384`,
383`ecdsa-sha2-nistp521` and `ssh-rsa` keys. A public key can be registered
384once on g1t: a key that is already someone's SSH key, or a deploy key on
385any repository, is refused with **Key is already in use.** Give each
386machine, and each repository, its own key. A repository can have up to
387100 deploy keys.
388
389### Manage deploy keys
390
391**Settings → Deploy keys** lists every key on the repository, oldest
392first, with its title, fingerprint, whether it is **Read-only** or **Read
393and write**, who added it and when, and when it was last used. **Last
394used** is when the key last signed in over SSH, to within 5 minutes;
395**Never used** means it never has. Use it to find keys nothing uses any
396more.
397
398To remove a key, choose **Delete** beside it and confirm. Anything using
399it stops at once.
400
401Only people with the Admin role on the repository see the page and
402manage its keys. An agent's token never can, and neither can a deploy key.
403A workspace's own access token can only when an owner gave it Admin. See
404[access and roles](/guides/access-and-roles/#deploy-keys). Adding and
405deleting a key is recorded in the workspace's
406[audit log](/guides/audit-log/) as `repo.deploy_key_added` and
407`repo.deploy_key_removed`.
408
409Deploy keys are kept by repository, so renaming or transferring the
410repository keeps them. While a repository is deleted its keys do not work,
411and when it is removed for good they go with it.
412
413### Use a deploy key with git
414
415Once SSH is on, point git at the key for the repository's remote:
416
417```sh
418GIT_SSH_COMMAND="ssh -i ~/.ssh/g1t_deploy -o IdentitiesOnly=yes" \
419 git clone git@g1t.sh:<workspace>/<repo>.git
420```
421
422Or name it in `~/.ssh/config` for every git command on that machine:
423
424```text
425Host g1t.sh
426 User git
427 IdentityFile ~/.ssh/g1t_deploy
428 IdentitiesOnly yes
429```
430
431A machine that needs several repositories needs a key for each; give each
432a `Host` alias with its own `IdentityFile`.
433
434### Through the API
435
436| Route | MCP tool and action | What it does |
437| --- | --- | --- |
438| `GET /repos/{owner}/{name}/keys` | `access` `list_deploy_keys` | The repository's deploy keys. |
439| `GET /repos/{owner}/{name}/keys/{id}` | `access` `get_deploy_key` | One key, by its `id`. |
440| `POST /repos/{owner}/{name}/keys` | `access` `add_deploy_key` | Add a key. Body: `title`, `key` and `read_only` (true unless you send false). |
441| `DELETE /repos/{owner}/{name}/keys/{id}` | `access` `remove_deploy_key` | Delete a key. |
442
443Listing and reading need the `access:read` scope; adding and deleting
444need `access:admin`. Each needs the Admin role on the repository.
445
446```sh
447curl https://api.g1t.sh/repos/acme/rocket/keys \
448 -H "Authorization: Bearer $G1T_TOKEN" \
449 -H "Content-Type: application/json" \
450 -d '{"title": "Build server", "key": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGb9ECWmEzf6FQbrBZ9w7lshQhqowtrbLDFw4rXAxZuE", "read_only": true}'
451```
452
453```json
454{
455 "id": "dk_01kp3f2g3h4j5k6m7n8p9q0r1s",
456 "title": "Build server",
457 "key": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGb9ECWmEzf6FQbrBZ9w7lshQhqowtrbLDFw4rXAxZuE",
458 "fingerprint": "SHA256:ubxEl41fJDnUoEPKSZE0y6R0ZjjAQf/wV5vZgeBV8qk",
459 "read_only": true,
460 "created_at": "2026-10-08T09:12:00.000Z",
461 "created_by": "ada",
462 "last_used_at": null
463}
464```
465
466See [create a deploy key](/reference/api/access/create-deploy-key/) in the
467API reference.