Skip to main content

Overview

The upload-pack endpoint implements the Git Smart HTTP protocol for fetch operations. This endpoint allows you to clone and pull from repositories through gitGost, which proxies requests directly to GitHub.
Upload-pack operations are read-only and do not modify repository state. They’re used for git clone, git fetch, and git pull operations.

Endpoints

Discovery: Get References

GET /v1/gh/:owner/:repo/info/refs?service=git-upload-pack
Returns available references (branches, tags) and server capabilities from GitHub.
owner
string
required
GitHub repository owner (user or organization)
repo
string
required
Repository name
service
string
required
Must be git-upload-pack

Request Headers

User-Agent
string
gitGost sends git/2.0 when proxying to GitHub

Response

Content-Type
string
application/x-git-upload-pack-advertisement
WWW-Authenticate
string
None - No authentication required
The response is proxied directly from GitHub and contains: Example Response (pkt-line format):
001e# service=git-upload-pack\n
0000
00a527d1f28...c7f refs/heads/main\0multi_ack thin-pack side-band side-band-64k ofs-delta shallow deepen-since deepen-not\n
003f5e8a91b...4d2 refs/heads/dev\n
003aabc123...def refs/tags/v1.0.0\n
0000
Implementation: internal/http/handlers.go:409-435 Flow:

Data Transfer: Fetch Objects

POST /v1/gh/:owner/:repo/git-upload-pack
Receives a request specifying desired commits (wants) and existing commits (haves), returns a packfile containing the requested objects.
owner
string
required
GitHub repository owner
repo
string
required
Repository name

Request Headers

Content-Type
string
required
application/x-git-upload-pack-request
Accept
string
application/x-git-upload-pack-result
User-Agent
string
gitGost sends git/2.0 when proxying

Request Body

The request body uses pkt-line format to specify: Example Request (decoded):
0098want 27d1f28...c7f multi_ack_detailed no-done side-band-64k thin-pack ofs-delta\n
0032want 5e8a91b...4d2\n
0000
0032have abc123...def\n
0009done\n

Response

Content-Type
string
application/x-git-upload-pack-result
The response is a packfile containing the requested objects, proxied directly from GitHub. Response Format:
PACK<version><num-objects><objects...><checksum>
  • Signature: PACK (4 bytes)
  • Version: 2 or 3 (4 bytes, network byte order)
  • Object Count: Number of objects (4 bytes)
  • Objects: Compressed object data
  • Checksum: SHA-1 of the preceding data (20 bytes)
Implementation: internal/http/handlers.go:437-476

Size Limits

To prevent abuse, gitGost enforces a size limit on upload-pack requests:
const maxUploadBytes = 50 * 1024 * 1024 // 50 MB
c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, maxUploadBytes)
Source: internal/http/handlers.go:441-442 If exceeded, the server returns:
{
  "error": "request body too large"
}
HTTP Status: 413 Request Entity Too Large

Proxy Architecture

gitGost acts as a transparent proxy for fetch operations:

Why Proxy?

Fetch operations only read data - they don’t expose user identity. There’s no commit metadata to strip.
Direct proxying is faster than re-serving packfiles. GitHub’s CDN delivers optimal performance.
Ensures 100% Git protocol compatibility since GitHub is the authoritative source.
Users always get the exact same data whether they fetch via gitGost or directly from GitHub.

Use Cases

Clone a Repository

git clone https://gitgost.leapcell.app/v1/gh/owner/repo.git
What happens:
  1. Git sends GET /info/refs?service=git-upload-pack to discover refs
  2. Git sends POST /git-upload-pack with wants (all branch/tag tips)
  3. gitGost proxies both requests to GitHub
  4. Git receives packfile and reconstructs repository

Fetch Updates

cd repo
git fetch gost
What happens:
  1. Git sends wants (remote branch tips) and haves (local commits)
  2. gitGost proxies request to GitHub
  3. GitHub calculates minimal packfile (only new commits)
  4. Git receives and integrates new commits

Pull Changes

git pull gost main
What happens:
  1. Git performs a fetch (as above)
  2. Git merges fetched changes into current branch

Shallow Clone

git clone --depth 1 https://gitgost.leapcell.app/v1/gh/owner/repo.git
Shallow clones are fully supported - gitGost proxies the deepen capability to GitHub.

Capabilities

GitHub (via gitGost) advertises these upload-pack capabilities:
multi_ack
capability
Server can acknowledge multiple common commits during negotiation
multi_ack_detailed
capability
Enhanced version with detailed negotiation feedback
thin-pack
capability
Server can send thin packs (packs with delta references to objects not in the pack)
side-band
capability
Multiplexed communication (progress + data)
side-band-64k
capability
Enhanced side-band with 64KB chunks
ofs-delta
capability
Pack can use offset deltas
shallow
capability
Server supports shallow clones
deepen-since
capability
Client can request commits since a date
deepen-not
capability
Client can request commits not reachable from refs
no-done
capability
Client doesn’t need to send ‘done’ in some scenarios
These are determined by GitHub, not gitGost.

Error Handling

Common Errors

Cause: GitHub is unreachable or returned an errorResolution:Code: internal/http/handlers.go:422-425 (discovery), internal/http/handlers.go:464-467 (data transfer)
Cause: Request exceeds 50 MB limitResolution: This should rarely happen in practice. If it does, the repository may have unusual characteristics. Clone directly from GitHub instead.Code: internal/http/handlers.go:445-447
Cause: Malformed request or connection errorResolution: Check network connection, update Git clientCode: internal/http/handlers.go:448-451
Cause: Repository path contains invalid charactersResolution: Ensure owner and repo names only contain alphanumeric characters, hyphens, underscores, and dotsCode: internal/http/router.go:78-92 (validation middleware)

Comparison: Fetch vs Push

AspectUpload-Pack (Fetch)Receive-Pack (Push)
DirectionServer → ClientClient → Server
OperationRead-onlyWrite (creates PR)
AnonymizationNone neededFull metadata stripping
gitGost RoleTransparent proxyActive processor
GitHub AuthNonegitGost bot account
Rate LimitingNone5 PRs/hour/IP
Use CasesClone, fetch, pullPush for PR creation

Implementation Details

Discovery Handler

func UploadPackDiscoveryHandler(c *gin.Context) {
    owner := c.Param("owner")
    repo := c.Param("repo")
    
    githubURL := fmt.Sprintf(
        "https://github.com/%s/%s.git/info/refs?service=git-upload-pack",
        owner, repo,
    )
    
    req, err := http.NewRequest("GET", githubURL, nil)
    if err != nil {
        c.AbortWithStatusJSON(http.StatusInternalServerError,
            gin.H{"error": "failed to build request"})
        return
    }
    req.Header.Set("User-Agent", "git/2.0")
    
    resp, err := uploadPackClient.Do(req)
    if err != nil {
        c.AbortWithStatusJSON(http.StatusBadGateway,
            gin.H{"error": "failed to reach GitHub"})
        return
    }
    defer resp.Body.Close()
    
    c.Writer.Header().Set("Content-Type",
        "application/x-git-upload-pack-advertisement")
    c.Writer.Header().Set("WWW-Authenticate", "None")
    c.Writer.WriteHeader(resp.StatusCode)
    io.Copy(c.Writer, resp.Body)
}
Source: internal/http/handlers.go:409-435

Data Transfer Handler

func UploadPackHandler(c *gin.Context) {
    owner := c.Param("owner")
    repo := c.Param("repo")
    
    const maxUploadBytes = 50 * 1024 * 1024
    c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, maxUploadBytes)
    body, err := io.ReadAll(c.Request.Body)
    if err != nil {
        if err.Error() == "http: request body too large" {
            c.AbortWithStatusJSON(http.StatusRequestEntityTooLarge,
                gin.H{"error": "request body too large"})
            return
        }
        c.AbortWithStatusJSON(http.StatusBadRequest,
            gin.H{"error": "failed to read body"})
        return
    }
    
    githubURL := fmt.Sprintf(
        "https://github.com/%s/%s.git/git-upload-pack",
        owner, repo,
    )
    
    req, err := http.NewRequest("POST", githubURL, bytes.NewReader(body))
    if err != nil {
        c.AbortWithStatusJSON(http.StatusInternalServerError,
            gin.H{"error": "failed to build request"})
        return
    }
    req.Header.Set("Content-Type", "application/x-git-upload-pack-request")
    req.Header.Set("Accept", "application/x-git-upload-pack-result")
    req.Header.Set("User-Agent", "git/2.0")
    
    resp, err := uploadPackClient.Do(req)
    if err != nil {
        c.AbortWithStatusJSON(http.StatusBadGateway,
            gin.H{"error": "failed to reach GitHub"})
        return
    }
    defer resp.Body.Close()
    
    c.Writer.Header().Set("Content-Type", "application/x-git-upload-pack-result")
    c.Writer.WriteHeader(resp.StatusCode)
    io.Copy(c.Writer, resp.Body)
}
Source: internal/http/handlers.go:437-476

HTTP Client Configuration

var uploadPackClient = &http.Client{
    Timeout: 30 * time.Second,
}
Source: internal/http/handlers.go:31
The 30-second timeout is sufficient for most operations. Large repositories may occasionally time out - in such cases, clone directly from GitHub.

Examples

Clone via gitGost

# Clone a repository
git clone https://gitgost.leapcell.app/v1/gh/torvalds/linux.git

# Output:
Cloning into 'linux'...
remote: Enumerating objects: 10234567, done.
remote: Counting objects: 100% (234/234), done.
remote: Compressing objects: 100% (156/156), done.
remote: Total 10234567 (delta 89), reused 198 (delta 78), pack-reused 10234333
Receiving objects: 100% (10234567/10234567), 3.2 GiB | 45.2 MiB/s, done.
Resolving deltas: 100% (8234567/8234567), done.

Add as Remote

cd my-repo
git remote add gost https://gitgost.leapcell.app/v1/gh/owner/repo.git

# Fetch from gitGost
git fetch gost

# Pull from gitGost
git pull gost main

Shallow Clone for CI

# Clone only the latest commit
git clone --depth 1 https://gitgost.leapcell.app/v1/gh/owner/repo.git

# Clone commits from last week
git clone --shallow-since="1 week ago" https://gitgost.leapcell.app/v1/gh/owner/repo.git

When to Use gitGost for Fetching

Consistent Workflow

Use the same remote for both fetch and push operations
git remote add gost https://gitgost.leapcell.app/v1/gh/owner/repo.git
git pull gost main
git push gost main

Testing

Test the full gitGost integration including fetch operations

Unified Interface

Build tools that interact with repositories exclusively through gitGost

Monitoring

Track all Git operations through a single endpoint
For most use cases, fetching directly from GitHub is simpler and may be faster. Use gitGost for fetching primarily when you need a unified interface or are testing the service.

Git Smart HTTP

Learn about the protocol gitGost implements

Receive-Pack

Push operations (git push)

Quickstart

Get started with gitGost in 2 minutes

Architecture

System architecture overview

Build docs developers (and LLMs) love