Skip to content
403 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## Browsing without an account
84
85Public projects, Explore, Search and profiles are open to everyone, in the
86same sidebar members use. Signed out, the sidebar has Explore and Search,
87and in a project its Code, Issues, Pull requests, Agents, Workflows and
88Deployments; pages only people with a role on the repository see, such as
89Security and Settings, are left out. **Sign in** and **Sign up** sit at the bottom, and both bring you
90back to the page you were on.
91
92## Protected branches
93
94A repository protects its branches and tags with [rulesets](/guides/rules/),
95under **Settings → Rules**. A push that breaks a rule is refused for
96everyone, whatever their role, and for agents, unless a ruleset lists them
97as able to bypass it. Git prints which ruleset and rule refused it, and how
98to fix it:
99
100```text
101remote: error: rules for refs/heads/main declined this push:
102remote: - Changes to main must be made through a pull request. [ruleset "Protect main", pull_request]
103remote: Push a branch, open a pull request into main, and merge it.
104 ! [remote rejected] main -> main (declined by ruleset "Protect main" (pull_request))
105```
106
107With **Require a pull request before merging**, changes reach a branch only
108by merging a pull request. Creating the branch, such as the first push to
109an empty repository, is still allowed. Rulesets also block force pushes and
110deletions, restrict who creates branches and tags, check commit messages,
111signatures and the files a push changes, and set what a merge needs. See
112[rules](/guides/rules/).
113
114## Branches
115
116Push any branch to a repository you can write to, and open a
117[pull request](/concepts/overview/#pull-requests) from it on the
118repository's **Pull requests** tab.
119
120```sh
121git switch -c my-change
122git push origin my-change
123```
124
125A repository's **Branches** tab, `g1t.sh/<workspace>/<repo>/branches`, lists
126every branch: the default one first, then those with a commit in the last 90
127days (**Active**), then the rest (**Stale**). Each shows its last commit, how
128many commits it is ahead of and behind the default branch, the pull request
129open on it with its checks, and its preview when it has one. A branch with
130no pull request links to opening one; one with nothing the default branch
131lacks (ahead `0`) says **Nothing to merge** instead. The counts are exact,
132merges included. g1t reads up to 1,000 commits of each history to find
133where the two meet; a branch that left the default branch further back
134than that shows no counts. Search narrows the list by name.
135
136The **Tags** tab lists tags newest first, up to 100, each with its commit
137and a ZIP of its files.
138
139**Compare**, `g1t.sh/<workspace>/<repo>/compare/<base>...<head>`, shows
140what one branch has that another does not: its commits, then every change.
141Pick the two branches at the top; **Open a pull request** starts one from
142the compared branch.
143
144On **Files**, each file and folder shows the commit that last changed it and
145when, from up to 300 commits of the branch's history; one changed before
146that shows none. The branch menu at the top switches branch and keeps the
147folder or file you are on.
148
149## Pull request forks
150
151A pull request that was not opened from a branch has its own remote:
152
153```text
154https://g1t.sh/pulls/<pull request id>.git
155```
156
157Only whoever opened the pull request can push to it, or, for one g1t
158made, whoever asked for it. Pushes to a fork
159update the pull request's head commit on its page.
160
161## Limits
162
163### Size limits
164
165Repositories are stored in Cloudflare Artifacts. g1t checks its limits
166before a push is stored, and declines a push that would cross one. git
167prints the reason beside each branch (`! [remote rejected] main (…)`), and
168what to do as `remote:` lines. Nothing in a declined push is stored.
169
170| Limit | Size | What happens past it |
171| --- | --- | --- |
172| A file | 32 MB | The push is declined, naming the file's size. |
173| 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. |
174| 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. |
175| A push | 100 MB | Refused by the network with HTTP `413` before g1t sees it. |
176
177To push a large history in parts:
178
179```sh
180git rev-list --reverse HEAD | awk 'NR % 500 == 0' | xargs -I{} git push origin {}:refs/heads/main
181git push origin main
182```
183
184Each push sends only what the one before did not.
185
186### When the store is busy
187
188If Cloudflare Artifacts is rate limiting g1t or not answering, g1t tries
189reads again for a moment, then answers git with HTTP `429` (rate limited)
190or `503` (unavailable) and a `Retry-After` header saying how many seconds
191to wait. Pushes are never tried again on your behalf: run `git push`
192again. On g1t.sh the page says the git storage is busy instead of failing,
193and [status.g1t.sh](https://status.g1t.sh) shows **Git storage**.
194
195### Git operations
196
197Each clone, fetch and push is a git operation, your agents' included:
198their sandboxes use the same git endpoints you do, and a pull request's
199working copy counts for its repository's workspace. Every workspace has 50,000
200a month included. Past that, a workspace on the g1t plan pays $0.18 per
2011,000, and a free workspace is never charged: past 50,000 in a month, its
202git requests past 60 in an hour are answered `429` with when to try again,
203until the month turns. Counting starts on 2026-10-14. See
204[git operations](/guides/usage-and-billing/#git-operations).
205
206What these limits mean in practice, and what to do instead, is on
207[What g1t can't do yet](/about/limitations/#git).
208
209## Where a slow request's time went
210
211Every answer g1t gives git carries a `Server-Timing` header: how many
212milliseconds each step of the request took. To see it, run git with its
213HTTP trace on:
214
215```sh
216GIT_TRACE_CURL=1 git ls-remote https://g1t.sh/<owner>/<repo>.git 2>&1 | grep -i server-timing
217```
218
219| Step | What it is |
220| --- | --- |
221| `repo` | Finding the repository, and checking your credentials if you sent any |
222| `moved` | Only for an address with no repository: looking for a renamed workspace or a transferred repository to send you to |
223| `access` | Deciding whether you may fetch from or push to it |
224| `kept` | A free workspace's limits, and looking for a ref listing and a store credential made a moment ago |
225| `mint` | Only when no credential was kept: the git store making one for the request |
226| `store` | The git store's answer; for a push, checking it for secrets first |
227| `refs` | Only for a push: recording that the repository's refs changed |
228| `total` | Everything g1t did |
229| `repos` | The same, measured where your request arrived |
230
231Two entries say how a step went rather than how long it took:
232
233| Entry | Values |
234| --- | --- |
235| `refs;desc=` | `hit-colo` or `hit-shared` when the ref listing came from g1t's cache, `miss` when the git store was asked |
236| `pack;desc=` | Only for a fresh clone: `hit` when its pack came from g1t's cache, `miss` when the git store built it |
237| `cred;desc=` | `isolate` or `shared` for a store credential made a moment ago, `mint` for a new one |
238
239The ref listing git asks for first on every clone and fetch is kept for up
240to a minute, and only the same question about the same refs gets the same
241answer: a push, a merge or any other change to a repository's branches and
242tags makes the next fetch ask the git store again. A change can take up to
2435 seconds to reach every fetch.
244
245A fresh clone, one that has no objects yet (shallow clones such as
246`git clone --depth=1` included), has its pack kept too, for up to 7 days
247or until the repository's branches or tags next change. The next clone that
248asks for the same commits in the same way gets the same pack without the
249git store building it again. A fetch into a repository you already have,
250and any pack over 200 MB, always goes to the git store.
251
252Include the header when you report a slow clone, fetch or push.
253
254## SSH
255
256Git over SSH is not available yet. Use HTTPS, which works for clone,
257fetch and push everywhere SSH would.
258
259Why: git over SSH needs raw TCP connections on port 22, and g1t runs
260entirely on Cloudflare's network. Accepting inbound TCP traffic directly
261into Workers is in a beta from Cloudflare that g1t has applied for and is
262waiting on. SSH keys can already be added under
263[Settings → SSH keys](https://g1t.sh/settings/keys),
264and will be used once SSH is on. So can [deploy keys](#deploy-keys).
265
266## Deploy keys
267
268A deploy key is an SSH key that reaches one repository and nothing else.
269Give one to a server or a pipeline that needs to clone a repository, or
270push to it, without a person's account behind it. A deploy key belongs to
271the repository: it keeps working when the person who added it leaves the
272workspace, and it is not tied to anyone's role.
273
274:::note
275Deploy keys are used over SSH, which is [not on yet](#ssh). You can add
276them now, and they will work as soon as SSH is. Until then, a machine can
277clone and push over HTTPS with a
278[workspace access token](/guides/workspaces/#workspace-access-tokens).
279:::
280
281### Read-only or read and write
282
283A deploy key is read-only unless you choose **Allow write access** when
284you add it:
285
286| Access | It can |
287| --- | --- |
288| **Read-only** (the default) | Clone and fetch the repository, private or not. |
289| **Read and write** | Clone, fetch and push, workflow files under `.g1t/workflows/` and `.github/workflows/` included. |
290
291Either way it reaches only its own repository: any other address is
292refused, in its workspace or anywhere else, and so is pushing to an
293address with no repository, which would otherwise make one.
294
295A key with write access can change workflows, and workflows run with the
296repository's secrets. Allow it only for a machine that must push. You
297cannot change a key's access later: delete it and add it again.
298
299### Add a deploy key
300
301You need the Admin role on the repository and a confirmed email address.
302
3031. Make a key pair on the machine that will use it, without a passphrase
304 if it runs unattended:
305
306 ```sh
307 ssh-keygen -t ed25519 -C "deploy@build-server" -f ~/.ssh/g1t_deploy -N ""
308 ```
309
3102. Open the repository's **Settings → Deploy keys**,
311 `g1t.sh/<workspace>/<repo>/settings/keys`.
3123. Under **Add a deploy key**, give it a **Title**, such as the machine that
313 uses it, and paste the public key (`~/.ssh/g1t_deploy.pub`) into **Key**.
314 Left without a title, it takes the key's comment.
3154. Choose **Allow write access** only if the machine must push.
3165. Choose **Add deploy key**.
317
318g1t takes `ssh-ed25519`, `ecdsa-sha2-nistp256`, `ecdsa-sha2-nistp384`,
319`ecdsa-sha2-nistp521` and `ssh-rsa` keys. A public key can be registered
320once on g1t: a key that is already someone's SSH key, or a deploy key on
321any repository, is refused with **Key is already in use.** Give each
322machine, and each repository, its own key. A repository can have up to
323100 deploy keys.
324
325### Manage deploy keys
326
327**Settings → Deploy keys** lists every key on the repository, oldest
328first, with its title, fingerprint, whether it is **Read-only** or **Read
329and write**, who added it and when, and when it was last used. **Last
330used** is when the key last signed in over SSH, to within 5 minutes;
331**Never used** means it never has. Use it to find keys nothing uses any
332more.
333
334To remove a key, choose **Delete** beside it and confirm. Anything using
335it stops at once.
336
337Only people with the Admin role on the repository see the page and
338manage its keys. An agent's token never can, and neither can a deploy key.
339A workspace's own access token can only when an owner gave it Admin. See
340[access and roles](/guides/access-and-roles/#deploy-keys). Adding and
341deleting a key is recorded in the workspace's
342[audit log](/guides/audit-log/) as `repo.deploy_key_added` and
343`repo.deploy_key_removed`.
344
345Deploy keys are kept by repository, so renaming or transferring the
346repository keeps them. While a repository is deleted its keys do not work,
347and when it is removed for good they go with it.
348
349### Use a deploy key with git
350
351Once SSH is on, point git at the key for the repository's remote:
352
353```sh
354GIT_SSH_COMMAND="ssh -i ~/.ssh/g1t_deploy -o IdentitiesOnly=yes" \
355 git clone git@g1t.sh:<workspace>/<repo>.git
356```
357
358Or name it in `~/.ssh/config` for every git command on that machine:
359
360```text
361Host g1t.sh
362 User git
363 IdentityFile ~/.ssh/g1t_deploy
364 IdentitiesOnly yes
365```
366
367A machine that needs several repositories needs a key for each; give each
368a `Host` alias with its own `IdentityFile`.
369
370### Through the API
371
372| Route | MCP tool and action | What it does |
373| --- | --- | --- |
374| `GET /repos/{owner}/{name}/keys` | `access` `list_deploy_keys` | The repository's deploy keys. |
375| `GET /repos/{owner}/{name}/keys/{id}` | `access` `get_deploy_key` | One key, by its `id`. |
376| `POST /repos/{owner}/{name}/keys` | `access` `add_deploy_key` | Add a key. Body: `title`, `key` and `read_only` (true unless you send false). |
377| `DELETE /repos/{owner}/{name}/keys/{id}` | `access` `remove_deploy_key` | Delete a key. |
378
379Listing and reading need the `access:read` scope; adding and deleting
380need `access:admin`. Each needs the Admin role on the repository.
381
382```sh
383curl https://api.g1t.sh/repos/acme/rocket/keys \
384 -H "Authorization: Bearer $G1T_TOKEN" \
385 -H "Content-Type: application/json" \
386 -d '{"title": "Build server", "key": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGb9ECWmEzf6FQbrBZ9w7lshQhqowtrbLDFw4rXAxZuE", "read_only": true}'
387```
388
389```json
390{
391 "id": "dk_01kp3f2g3h4j5k6m7n8p9q0r1s",
392 "title": "Build server",
393 "key": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGb9ECWmEzf6FQbrBZ9w7lshQhqowtrbLDFw4rXAxZuE",
394 "fingerprint": "SHA256:ubxEl41fJDnUoEPKSZE0y6R0ZjjAQf/wV5vZgeBV8qk",
395 "read_only": true,
396 "created_at": "2026-10-08T09:12:00.000Z",
397 "created_by": "ada",
398 "last_used_at": null
399}
400```
401
402See [create a deploy key](/reference/api/access/create-deploy-key/) in the
403API reference.