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:
Identifies the bundle format:
or
Detect Bundle Version
Shell Detection
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
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
Verify before distribution
Always run git bundle verify before sharing bundles to ensure completeness.
Use v3 for modern features
Specify --version=3 when using SHA-256 or partial clones.
Include descriptive comments
Add helpful comments to prerequisites for human readers.
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
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