Move Git History Offline with Verified Bundle Files
You will create a Git bundle on one machine, verify what it needs, move it across an offline boundary, and fetch its commits into another repository. This guide uses Git 2.43.0, the version installed while it was written. Allow about 15 minutes for a small repository, plus the time needed to copy the resulting file.
The route
Jump straight to the step you need, or tick off Done means at the end.
What a bundle does
A bundle is a file containing Git references and a pack of objects. It is useful when the two repositories cannot communicate directly, but a file can be carried by removable media, a controlled transfer service or another approved route. It is not a replacement for a remote repository: Git can fetch from a bundle, but it cannot push into one.
The bundle format records the reference tips that the recipient can fetch. It can also record prerequisite objects that are deliberately absent from the file. That makes incremental bundles smaller, but it means the receiving repository must already contain the required history.
Checkpoint
Use a full bundle when the destination is new or its existing history is uncertain. Use an incremental bundle only when you can name, and verify, the destination's last known commit.
1. Prepare the source repository
Run ordinary commands as your repository user. Change the two placeholders before running them. This example exports the main branch from a source checkout.
SOURCE=/path/to/source-repository
BUNDLE=/path/to/transfer/source-main.bundle
cd "$SOURCE"
git status --short
git log -1 --oneline main
An empty status is a useful checkpoint, but a dirty working tree does not change the committed objects that a bundle contains. It is still worth stopping if you expected a commit to be included and it is not visible in the log. The bundle contains commits and related objects, not uncommitted file changes.
2. Create a complete bundle
Create a self-contained starting point with the branch name as the revision argument:
cd "$SOURCE"
git bundle create "$BUNDLE" main
Git should report no error and leave a file at the path in BUNDLE. The destination can clone this kind of bundle without first having any of its objects.
Safety warning
The output path is overwritten if it names an existing file. Do not point it at your only copy of a previous bundle. Choose a new filename, or preserve the old file until the new one has passed verification and transfer checks.
Inspect the references before transferring the file:
git bundle list-heads "$BUNDLE"
git bundle verify "$BUNDLE"
For a complete bundle, verification should identify the advertised branch and should not complain about missing prerequisite commits. The exact object ID depends on the source repository. If verification fails, do not transfer the file as if it were usable; check the path, repository and revision argument first.
3. Transfer and verify the file
Copy the bundle through your approved offline route. If you use removable media, unmount it cleanly after copying. On the destination machine, set the path to the received file and verify it before changing the repository:
BUNDLE=/path/to/received/source-main.bundle
git bundle list-heads "$BUNDLE"
git bundle verify "$BUNDLE"
sha256sum "$BUNDLE"
Compare the checksum with the value calculated on the source machine, using a trusted channel. A checksum mismatch means the file changed or the two values were confused. Copy it again rather than trying to repair the bundle.
Do not treat a successful checksum as proof that the contents are appropriate. list-heads tells you which references are offered, while verify checks whether the receiving repository has the prerequisites. Read both results before importing anything.
4. Import into an existing repository
Make a safety copy or confirm that the destination repository has a working backup before fetching. A fetch updates references and object storage, so it changes repository state even though it does not rewrite your current branch.
DEST=/path/to/destination-repository
cd "$DEST"
git bundle verify "$BUNDLE"
git fetch "$BUNDLE" main:refs/heads/offline-main
git show-ref refs/heads/offline-main
The refspec maps the bundle's main reference to a new local branch named offline-main. The final command should print the new branch's object ID and full ref name. Inspect it before merging or switching to it:
git log --oneline --decorate -n 10 offline-main
If the destination already has the objects required by an incremental bundle, the fetch can proceed. If git bundle verify reports missing prerequisites, stop. Fetch the earlier full bundle, or create a new incremental bundle from a commit the destination really has.
5. Build an incremental bundle
For repeated transfers, keep a tag at the last commit successfully received by the destination. The tag must already exist at that older commit when you create the next bundle. For example, after the destination accepted an earlier transfer, create the next file like this:
cd "$SOURCE"
git bundle create "$BUNDLE" last-offline-transfer..main
This command asks Git for commits reachable from main but not from the old tag. The resulting bundle can be smaller. After the destination has successfully imported this file, advance the tag on the source to the new tip:
git tag -f last-offline-transfer main
Do not advance the tag before the destination accepts the bundle. Moving it early can create a gap that the next bundle does not contain. If this is the first transfer, create a complete bundle instead, or set the tag to the destination's confirmed starting commit before making an incremental one.
Before sending the incremental file, run the same checks:
git bundle list-heads "$BUNDLE"
git bundle verify "$BUNDLE"
An incremental bundle normally has a prerequisite line. The prerequisite is not a shallow-clone boundary. It is an object the reader must already possess, and the bundle may use it for reachability or delta storage. A shallow repository is therefore not automatically a suitable recipient.
Common errors and recovery
"Repository lacks these prerequisite commits"
The destination does not contain the history named by the bundle. Fetch the missing earlier bundle, use a complete bundle, or regenerate the incremental file from a commit the destination can prove it has. Do not delete the destination's refs to make verification appear simpler.
The expected branch is absent
Check the source revision and the bundle's advertised refs with git bundle list-heads. A bundle records the refs selected when it was created; a later branch update does not alter the file. Create a replacement bundle if the source moved on.
The fetch changed the wrong local ref
Review the refspec before running git fetch. If you deliberately created a temporary branch and want to remove only that ref, first confirm its name, then run:
git branch -D offline-main
This removes the local branch name, not necessarily the underlying objects immediately. It is destructive to that branch reference, so do not run it until you have checked that no work depends on it. Re-fetch the bundle with a corrected refspec if needed.
Done means
- The source bundle was created from the intended repository and revision.
git bundle list-headsshows the expected reference.git bundle verifypasses on the receiving repository.- The transferred file's checksum matches the source value.
- The fetched reference was inspected before any merge or branch switch.
- Any incremental tag was advanced only after the destination accepted the previous bundle.