git bundle creates a single portable file containing a Git repository or a subset of its history — branches, tags, and all referenced objects — that can be transferred on a USB drive, email, or any medium that does not support Git protocol. For WordPress deployments in air-gapped environments (no internet on the production server), bundles provide a reproducible, verifiable alternative to rsync or FTP.
Problem: A WordPress plugin needs to be deployed to an air-gapped server with no internet access — git clone and git fetch cannot reach the origin repository, and there is no way to transfer Git history via standard file copy.
Solution: Use git bundle create repo.bundle --all to pack the entire repository — all branches, tags, and history — into a single binary file. Transfer it via USB, SCP, or any file transport. On the target machine, git clone repo.bundle creates a full repository clone, and subsequent incremental bundles can be created with git bundle create update.bundle origin/main..HEAD.
The commands below create a full repository bundle, create an incremental bundle (only new commits since the last deployment), verify a bundle before use, clone from a bundle, and fetch updates from a bundle into an existing repository.
# ── 1. Create a full repository bundle ───────────────────────────────────
cd /path/to/my-wordpress-plugin
git bundle create /tmp/my-plugin-full.bundle --all
# Creates a self-contained file with all branches, tags, and history
# ── 2. Verify the bundle is valid before transferring ────────────────────
git bundle verify /tmp/my-plugin-full.bundle
# Output: "The bundle contains this complete history" (or lists prerequisites)
# ── 3. Clone from the bundle on the air-gapped server ────────────────────
# Copy my-plugin-full.bundle to the target server (USB / SCP to jump host)
git clone /media/usb/my-plugin-full.bundle my-plugin
cd my-plugin
git remote set-url origin git@github.com:myorg/my-plugin.git # restore for future use
# ── 4. Incremental bundle: only commits since the last deployment ─────────
# On the source machine — record the last-deployed commit hash
LAST_DEPLOYED=$(cat /var/deploy/last-commit.txt)
git bundle create /tmp/my-plugin-update.bundle \
"$LAST_DEPLOYED"..HEAD \
--branches --tags
# Only includes objects reachable from HEAD but not from LAST_DEPLOYED
# Save new HEAD for next incremental
git rev-parse HEAD > /var/deploy/last-commit.txt
# ── 5. Apply the incremental bundle on the server ────────────────────────
cd /var/www/wp-content/plugins/my-plugin
git bundle verify /media/usb/my-plugin-update.bundle
git fetch /media/usb/my-plugin-update.bundle 'refs/heads/*:refs/remotes/bundle/*'
git merge remotes/bundle/main
# ── 6. Bundle only a specific branch (e.g., release branch) ─────────────
git bundle create /tmp/release.bundle main --tags
# Only main branch + all tags; other branches excluded
# ── 7. List the contents of a bundle ─────────────────────────────────────
git bundle list-heads /tmp/my-plugin-full.bundle
NOTE: An incremental bundle requires the receiving repository to already contain the prerequisite commits (the LAST_DEPLOYED hash and all its ancestors) — if the receiving repo was reset or the prerequisite commit was garbage-collected, the bundle verification will fail with "The bundle requires this history"; always keep the full bundle as a fallback and use git bundle verify before every fetch.