Skip to main content

Overview

Git bundles provide a portable format for transferring repository data without requiring network access. A bundle file packages refs and objects together, enabling offline cloning, fetching, and archival.
Bundle files typically use .bundle or .bdl extensions and can be used with git clone, git fetch, and git pull.

Use Cases

Offline Transfer

Move repositories via USB drives, email, or secure file transfer when network access is unavailable.

Backup

Create portable backups of repository state including refs and history.

Air-Gapped Systems

Transfer code to/from systems isolated from networks for security.

Incremental Updates

Create incremental bundles containing only new commits since last bundle.

Bundle Versions

Git supports two bundle format versions:

Version 2 (v2)

The original bundle format:
bundle    = signature *prerequisite *reference LF pack
signature = "# v2 git bundle" LF

prerequisite = "-" obj-id SP comment LF
comment      = *CHAR
reference    = obj-id SP refname LF

pack         = ... ; packfile

Version 3 (v3)

Adds capability support for extended features:
bundle    = signature *capability *prerequisite *reference LF pack  
signature = "# v3 git bundle" LF

capability   = "@" key ["=" value] LF
prerequisite = "-" obj-id SP comment LF
comment      = *CHAR
reference    = obj-id SP refname LF
key          = 1*(ALPHA / DIGIT / "-")
value        = *(%01-09 / %0b-FF)

pack         = ... ; packfile
Version 3 bundles can specify:
  • Hash algorithm via object-format capability
  • Partial clone filters via filter capability
Version 2 bundles always use SHA-1 and include complete objects.

Bundle Structure

A bundle file contains four sections:

1. Signature (Header)

Identifies the bundle format:
# v2 git bundle\n
or
# v3 git bundle\n
def read_bundle_version(filepath):
    with open(filepath, 'rb') as f:
        header = f.readline()
        if header == b'# v2 git bundle\n':
            return 2
        elif header == b'# v3 git bundle\n':
            return 3
        else:
            raise ValueError(f"Invalid bundle signature: {header}")

2. Capabilities (v3 only)

Define bundle requirements:
@object-format=sha256
@filter=blob:limit=1m

Recognized Capabilities

Specifies hash algorithm:
  • sha1 - 20-byte SHA-1 (default for v2)
  • sha256 - 32-byte SHA-256
Matches extensions.objectFormat configuration.
Indicates partial bundle with object filter:
  • blob:none - No blobs included
  • blob:limit=<n> - Blobs smaller than n bytes
  • tree:0 - No trees included
  • sparse:oid=<id> - Sparse checkout filter
The unbundled pack MUST be marked as .promisor.
Unknown capabilities cause git bundle to abort. This ensures bundles aren’t incorrectly processed by older Git versions.

3. Prerequisites

Objects NOT included that the reader MUST have:
-a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0 Base commit for feature
-b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0a1 Merge base
Format:
"-" <40-hex-sha1> SPACE <comment> LF
The comment is purely descriptive and MUST be ignored by readers. Writers MAY include commit subject lines or other helpful text.

Prerequisite Semantics

Objects in the bundle may:
  • Reference prerequisite objects directly
  • Reference anything reachable from prerequisites
  • Use prerequisites as delta bases

4. References

Tips of history graphs included in the bundle:
a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0 refs/heads/main
b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0a1 refs/heads/feature
c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0a1b2 refs/tags/v1.0
Format:
<40-hex-sha1> SPACE <refname> LF
These define what can be git fetched from the bundle.

5. Pack Data

After a final LF, the packfile begins:
"PACK" <version> <object-count> <objects> <checksum>
The packfile contains all objects reachable from references that aren’t reachable from prerequisites. See gitformat-pack for packfile format details.

Creating Bundles

Full Repository Bundle

Include all history:
git bundle create repo.bundle --all
Generated bundle (v2):
# v2 git bundle
a1b2c3d4... refs/heads/main
b2c3d4e5... refs/heads/develop  
c3d4e5f6... refs/tags/v1.0
<LF>
PACK...

Incremental Bundle

Bundle commits since a tag:
git bundle create incremental.bundle v1.0..main
Result:
# v2 git bundle
-c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0a1b2 v1.0
a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0 refs/heads/main
<LF>
PACK...
Git automatically determines prerequisites based on the negative refs in the bundle specification:
  • v1.0..main → Prerequisite: commits reachable from v1.0
  • ^feature main → Prerequisite: commits reachable from feature

SHA-256 Bundle (v3)

git bundle create --version=3 repo-sha256.bundle --all
Result:
# v3 git bundle
@object-format=sha256
a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0a1b2c3d4e5f6g7h8i9j0 refs/heads/main
<LF>
PACK...

Partial Clone Bundle (v3)

git bundle create --version=3 --filter=blob:limit=100k partial.bundle --all
Result:
# v3 git bundle
@filter=blob:limit=100k
a1b2c3d4... refs/heads/main
<LF>
PACK...
After unbundling, mark the repository as partial:
git config core.repositoryformatversion 1
git config extensions.partialclone origin

Using Bundles

Verify Bundle

Check prerequisites and validity:
git bundle verify repo.bundle
Output:
The bundle contains these 2 refs:
a1b2c3d4... refs/heads/main
b2c3d4e5... refs/heads/feature
The bundle requires these 1 ref:
c3d4e5f6... (present in repository)
Verification fails if any prerequisites are missing from the repository.

Clone from Bundle

git clone repo.bundle new-repo
cd new-repo
Creates a new repository with all refs from the bundle.

Fetch from Bundle

Add bundle commits to existing repository:
git fetch /path/to/incremental.bundle refs/heads/main:refs/remotes/bundle/main

List Bundle Contents

git bundle list-heads repo.bundle
Output:
a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0 refs/heads/main
b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0a1 refs/heads/feature

Parsing Bundles

import struct

class BundleParser:
    def __init__(self, filepath):
        self.filepath = filepath
        self.version = None
        self.capabilities = {}
        self.prerequisites = []
        self.references = []
    
    def parse(self):
        with open(self.filepath, 'rb') as f:
            # Read signature
            signature = f.readline()
            if signature == b'# v2 git bundle\n':
                self.version = 2
            elif signature == b'# v3 git bundle\n':
                self.version = 3
            else:
                raise ValueError("Invalid bundle")
            
            # Read capabilities (v3 only)
            if self.version == 3:
                while True:
                    line = f.readline()
                    if not line.startswith(b'@'):
                        f.seek(f.tell() - len(line))  # rewind
                        break
                    
                    cap = line[1:].rstrip(b'\n').decode()
                    if '=' in cap:
                        key, value = cap.split('=', 1)
                        self.capabilities[key] = value
                    else:
                        self.capabilities[cap] = True
            
            # Read prerequisites and references
            while True:
                line = f.readline()
                if line == b'\n' or line == b'':
                    break
                
                if line.startswith(b'-'):
                    # Prerequisite
                    parts = line[1:].rstrip(b'\n').split(b' ', 1)
                    oid = parts[0].decode()
                    comment = parts[1].decode() if len(parts) > 1 else ''
                    self.prerequisites.append((oid, comment))
                else:
                    # Reference
                    oid, refname = line.rstrip(b'\n').split(b' ', 1)
                    self.references.append((oid.decode(), refname.decode()))
            
            # Remaining data is packfile
            pack_offset = f.tell()
            return pack_offset

# Usage
parser = BundleParser('repo.bundle')
pack_offset = parser.parse()
print(f"Version: {parser.version}")
print(f"Capabilities: {parser.capabilities}")
print(f"Prerequisites: {len(parser.prerequisites)}")
print(f"References: {len(parser.references)}")
print(f"Pack starts at byte: {pack_offset}")

Shallow Clones and Bundles

Bundle format v2 cannot represent shallow clone boundaries. Prerequisites have different semantics than shallow boundaries.When bundling from a shallow clone, the bundle will include commits down to the shallow boundary, but not mark them specially.

Bundle Best Practices

1

Verify before distribution

Always run git bundle verify before sharing bundles to ensure completeness.
2

Use v3 for modern features

Specify --version=3 when using SHA-256 or partial clones.
3

Include descriptive comments

Add helpful comments to prerequisites for human readers.
4

Test unbundling

Verify bundles can be unbundled in a clean repository before distribution.

Example Workflows

Daily Backup

#!/bin/bash
# Create dated backup bundle
DATE=$(date +%Y-%m-%d)
git bundle create "backup-${DATE}.bundle" --all

# Verify it
git bundle verify "backup-${DATE}.bundle"

Incremental Updates

# Initial full bundle
git bundle create initial.bundle --all
git tag bundle-checkpoint-1

# Later, incremental bundle
git bundle create update-2024-01.bundle bundle-checkpoint-1..main
git tag bundle-checkpoint-2

# Apply updates
git fetch initial.bundle "refs/*:refs/*"
git fetch update-2024-01.bundle "refs/heads/main:refs/heads/main"

Air-Gapped Transfer

# On connected system
git bundle create transfer.bundle main ^origin/main

# Copy transfer.bundle to air-gapped system

# On air-gapped system
git bundle verify transfer.bundle
git fetch transfer.bundle refs/heads/main:refs/heads/main

Comparison with Other Formats

Bundle

✓ Portable, self-contained ✓ Works with git commands ✗ Single file, no streaming

Clone --mirror

✓ Complete repository copy ✓ All refs and objects ✗ Requires filesystem access

Archive

✓ Small, no history ✓ Works without Git ✗ No version control data