Git Bundle: Transferring Repositories Offline Without a Server
AI generated
git
HEAD
Git
Git Bundle
Transferring repositories offline without a server

Not every environment has direct network access to a Git server, whether due to security constraints inside isolated networks or simply the lack of an internet connection. Git bundle packs commits, trees, and blobs into a single file that can be passed along via USB stick, email, or any other transport channel and hooked up like a regular remote.

9 min read Git Offline Workflow

1. When there is no direct access to the Git server

In isolated networks with no internet connection, at clients with strict security policies, or while traveling without a stable connection, a normal git push or git pull against a central server simply is not possible. Yet code still often needs to move between two environments, for instance between an isolated development environment and a production system with no outbound network access at all.

Git bundle was built precisely for such scenarios: the command packs a defined slice of history, including every needed commit, tree, and blob object, into a single binary file that can be transported over any channel and, on the receiving end, hooked up like a regular remote without any network access.

2. Creating and importing a complete bundle

The simplest case is a bundle containing the full history of one or more branches. The command git bundle create expects a file name and a revision specification that works exactly like it does for git log or git rev-list, so either a single branch name for that branch's complete history, or --all for every reference in the repository.

On the receiving side, the bundle can be cloned directly like a regular repository, or hooked into an already existing repository as an additional remote. Git treats the bundle file transparently as a network source, so all the usual commands like git fetch and git pull work without any change.


# Create a complete bundle with all branches and tags
git bundle create repo-full.bundle --all

# Clone the bundle like a normal repository
git clone repo-full.bundle project-local

# Hook the bundle into an existing repository as an additional remote
git remote add offline-source repo-full.bundle
git fetch offline-source

3. Verifying bundle integrity before handing it over

Before a bundle gets handed off, it is worth verifying it with git bundle verify, which checks whether the contained objects are consistent and whether every referenced ancestor is either included in the bundle itself or already present in the target repository. If a needed ancestor is missing, the command reports that exact missing commit instead of failing with a cryptic error only during import on the receiving end.

This check matters especially for incremental bundles built on a shared base commit, because an incorrectly chosen base commit would otherwise only surface for the recipient once the transfer itself is already complete and the physical medium may no longer be readily available.


# Check a bundle file for consistency and required ancestors
git bundle verify repo-full.bundle

# List contained references and commits without importing the bundle
git bundle list-heads repo-full.bundle

4. Incremental bundles for repeated transfers

For regular data exchange with an isolated environment, a full bundle on every transfer is wasteful, since already transferred commits get sent again each time. Git bundle therefore supports incremental transfers: with a revision specification such as last-sync..main, the bundle only contains the commits added since the last sync.

This requires the base commit, meaning the state of the last successful sync, to already exist in the target repository. A proven pattern is a permanently maintained tag or branch that marks the most recently transferred state and gets updated after every successful transfer.


# Pack only the commits since the marked last sync state into a bundle
git bundle create update-week-32.bundle last-sync..main

# Update the sync marker after a successful transfer
git tag -f last-sync main

# Import the incremental bundle on the receiving side
git fetch update-week-32.bundle main:main

5. Packing several branches and tags deliberately into one bundle

A bundle is not limited to a single branch. Several revision specifications can be combined in one call to transfer exactly the branches and tags that are actually needed on the receiving end, without dragging along unnecessary weight from feature branches that are not relevant there.

When importing such a multi reference bundle, git bundle list-heads shows in advance which branches and tags it contains, so the actually desired references can be picked deliberately with git fetch bundle-file ref:ref instead of automatically pulling in every reference the bundle carries.


# Create a bundle with main, a release branch, and all tags
git bundle create release-package.bundle main release/2.4 --tags

# Only pull main from the bundle into the local main branch
git fetch release-package.bundle main:main

6. Use in air gapped and heavily regulated environments

In regulated industries such as finance or critical infrastructure, production systems are often deliberately disconnected from the internet, so called air gapped environments. Code changes have to be brought in through a controlled, often manually approved channel there, which git bundle maps well when combined with an approval process and signed commits, since the bundle's integrity can be verified before it gets adopted.

A proven pattern is a two stage process: a bundle gets created in the open environment, digitally signed or at least given a checksum, moved into the air gapped environment through an approved transport path, and only imported there after successful verification with git bundle verify.


# Generate a checksum for the bundle for transport
sha256sum release-package.bundle > release-package.bundle.sha256

# Verify the checksum on the target side before importing the bundle
sha256sum -c release-package.bundle.sha256

7. Bundles versus patch files and git format-patch

For a single, small change, a bundle is often overkill. The command git format-patch turns one or more commits into classic patch files in mailbox format, which can be sent by email and applied on the receiving end with git am, without needing a full bundle for that. For individual commits or a short series, this route is lighter weight and easier for reviewers to read in plain text than a binary bundle file.

Once several branches, tags, or a longer commit series with a complex merge history need to be transferred, though, format-patch reaches its limits, since merge commits and tree structure can get lost along the way. Bundles represent the full object structure including merges without any loss, and are therefore the more robust choice whenever more than a single linear commit chain needs to move.


# Turn individual commits into patch files for sending by email
git format-patch -3 --stdout > last-three-commits.patch

# Apply the patch file on the receiving end
git am last-three-commits.patch

8. Best practices for using bundles in production

A clear naming convention for bundle files, for example including the date and the contained branches in the file name, prevents mix ups whenever several bundles are in circulation. For recurring transfers, a script that wraps bundle creation, checksum generation, and sync marker updates into a single step is worth building instead of running every step manually.

Bundles should generally be verified with git bundle verify before being handed off, because a broken or incomplete bundle often only surfaces on the receiving end after a failed import, at which point the original transport path may no longer be available anymore.

9. Common pitfalls when working with bundles

The most common mistake with incremental bundles is an incorrect or outdated base commit, which leads the target side to expect commits that are not actually included in the bundle. The error message on import is often not very informative in such cases, which is why a prior check with git bundle verify is worth doing every time.

Another pitfall is assuming a bundle automatically includes all tags or all branches: without explicitly specifying --all or concrete references, git bundle create only packs the specified revisions, which quickly leads to a bundle missing important references if the revision specification is incomplete.

Approach Network required Includes history Typical use
git push/pull over network Yes, continuously Yes, full or incremental Everyday team workflow
Full bundle No, only to transport the file Yes, the entire specified range Initial transfer into an isolated environment
Incremental bundle No, only to transport the file Only new commits since the base Recurring sync with an air gapped system
Patch files via email No Only individual commits as patches Individual changes without repository context

Mironsoft

Git workflows, branching strategies, and CI hooks

Chaotic Git history and unclear branching rules across the team?

We set up clean Git workflows, clarify branching strategies for the team, and automate quality checks via Git hooks and CI pipelines so the history stays traceable.

Workflow Audit

Review the existing branching strategy and merge practice for weak spots.

Hook Automation

Set up pre-commit and pre-push hooks for linting, tests, and commit conventions.

Team Training

Teach rebase, cherry-pick, and conflict resolution hands-on across the team.

10. Summary

Git Bundle

Core command

git bundle create file.bundle

Verify before handoff

git bundle verify file.bundle

Importing

git clone or git fetch like a normal remote

Typical use

Air gapped systems, missing network connectivity

11. FAQ: Git Bundle

1What exactly does a bundle file contain?
A bundle contains the specified commits together with their associated tree and blob objects, plus reference information for branches and tags, essentially all the data needed to reconstruct the included history in another repository.
2Can I clone a bundle like a normal repository?
Yes, git clone accepts a bundle file path just as it accepts a URL. The result is a complete local repository containing the history stored in the bundle.
3Why does importing an incremental bundle sometimes fail?
Usually because the base commit the bundle assumes is missing from the target repository. A prior check with git bundle verify surfaces this problem before the actual transfer even takes place.
4Does git bundle create --all automatically include all tags too?
Yes, the --all flag includes every reference in the repository, branches and tags alike, whereas specifying a single branch without --tags leaves tags out entirely.
5Is a Git bundle comparable to a git clone --mirror?
Only to a degree: a bundle is a single portable file meant for transport, while a mirror clone produces a complete, directly usable repository with all references on disk.
6Can I sign a bundle to verify its origin?
Git bundle itself has no built in signing feature, but the file can be signed externally with GPG or given a checksum to verify integrity and origin during transport.
7What happens if I try to import a bundle without running git bundle verify first?
The import still works without prior verification, but automatically fails as soon as a required base commit is missing in the target repository. Verify only surfaces this problem earlier and with a clearer error message.
8Can a bundle be larger than the actual repository?
No, a bundle is essentially a compressed pack file with the same objects that exist in the repository, so its size roughly matches that of an equivalent clone for the same slice of history.
9Is git bundle suitable for transferring very large repositories?
In principle yes, though without the benefits of partial or shallow clone, since a bundle always contains the full objects for the specified range. For very large histories, a targeted revision specification is worth using instead of --all.
10Can I import several bundles into the same repository one after another?
Yes, that is actually the common use case for recurring incremental transfers. Each bundle gets imported via git fetch, as long as its respective base commit is already present in the target repository.