MOOSSin
Menu

All posts · Open Source Tools

`git bundle`: This allows you to move an entire repository to another machine without specifying a remote.

Git's built-in bundle packages history into an archive that can be verified, fetched, and only fast-forwarded. This article explains how to use it, why I use it as the sole code entry point on my production machine, line endings and permission issues encountered when switching from Windows to Linux, and includes an analogy about sending a package.

Most people use Git in roughly the same way:git push Go up,git pull Next, there's a place called GitHub or GitLab. After using push and pull for a while, you might feel that that's all there is to Git, as if code is inherently meant to flow between two machines via the internet.

However, Git has had a rarely used command since version 1.5.1 in 2007.git bundle It packages the history of a repository into a single archive, which can then be accessed at the other end.git fetch It's like dealing with a remote file. The difference is that it's a file; you can copy, mix, save, put on a USB drive, email it, or upload it to a cloud drive. When I first learned about this feature, it felt like discovering a door in my house that I'd never opened before.

Mailing a parcel, not opening a pipe

Push and pull are like connecting a water pipe between two machines, so the water can flow continuously. Bundling is like sending a package: you pack your things in a box, seal it, write down the weight and contents, and send it over. Before the recipient signs for it, they confirm that the box is not damaged and that the weight is the same as on the slip.

Water pipes are convenient, but the price is that anyone can connect them and run water. Parcels are slower, but each parcel represents a conscious action, with documentation and a signature. Which is better depends on what's on the other end of the tap. If the other end is a regular machine with readers every day, then I'll accept the parcel.

Three commands

git bundle create site.bundle mainPackage the complete history of the main branch. Transfer it to another machine.git bundle verify site.bundle Check if it is complete and if the current repository has the required prior commits;git fetch site.bundle main Pull the submission in, thengit merge --ff-only FETCH_HEAD .

--ff-onlyIt's worth elaborating on this point. It means that it only accepts situations where "the other party's history is an extension of mine," and rejects any attempts to merge. In simpler terms: the contents of your package must follow what I already have; you can't ask me to dismantle and rearrange my own things.

Why didn't I add remote?

The production repository doesn't have a remote, which I did intentionally. Having a remote means there's the possibility of pushes, and that a tool, a script, or a branch that looks like main might update the production repository without my notice. This doesn't happen often, but if it does happen even once, it's enough to haunt you for a lifetime.

The bundled process is a bit slower: packaging, calculating SHA-256, copying, the recipient recalculates, comparing, verifying, fetching, and setting it to FF-only. Each step leaves a trace, and every change has a surviving record. This slight friction is exactly what I need. It's like how some people keep their credit cards in hard-to-reach places—not because they can't reach them, but because they think about it for a second longer when they need them.

The bundle can also move backwards. At the remote end...git bundle create - main Write the bundle to standard output and stream it back to the local machine via SSH; not a single byte needs to be written to the remote end. The first time I did this, I felt a little smug, like using a straw to sip a drink from the next table without even touching the other person's cup.

Two pitfalls in cross-platform development

Both of these pitfalls are related to Windows, and they are the kind of pitfalls where "everything seems to be correct."

The first is the end of the line. Git on Windows is always running.core.autocrlf=true Text files are CRLF in the working tree and are converted back to LF upon commit. This works fine for most files, but shell scripts and systemd unit files, once CRLFed in the Windows working tree but not properly normalized, will cause problems in the first line of Linux.#!/bin/sh Then there's one more thing you can't see.\r When executing, it will spray "bad interpreter". Look at that line; it looks exactly like the one in the tutorial because that character is completely invisible. The solution is....gitattributes Inside*.sh ,*.service ,*.timer This type of file is writtentext eol=lf and usegit ls-files --eol Check the actual status of the working tree.

The second is execution permissions. Windows' file system does not have the concept of an execution bit, so a script added in Windows will have a bit of 100644 after entering the index, and cannot be executed directly on Linux.git update-index --chmod=+x script.sh Pin 100755 into the index and bundle it along with the content. I copied this instruction onto a sticky note and stuck it there for a long time because I always needed it late at night, and I don't remember anything then.

Mini-drama: Isn't this just compressing a folder and sending it over?

"So a bundle is basically zipping the entire folder together?"

"No, zip compresses the file as it is now; bundle packages the entire history, including every commit, who made the changes, and when."

"Then why do I need history? I only want the latest information."

"Because once the other party receives it, they can verify that the package matches the part they have. ZIP can't do that; ZIP can only trust you."

"So a bundle is a package with a receipt."

"And the receipt states which period of history it grew from."

A lesson about working directories

When several repositories are placed side-by-side in the same folder, shells running in parallel may share the same working directory. I once encountered a situation where a commit ended up in a neighboring repository, and I also accidentally created a bundle with a misnamed name. The feeling upon realizing it was roughly like submitting homework to the inbox of the class next door, and even writing the name correctly on the envelope. Since then, my rule is to only perform one cross-repository Git operation at a time, and always use absolute paths.

Who is it suitable for?

It's suitable for any situation where you need to transfer code between two machines but don't want one of them to be able to push it. It's also suitable for archiving deployed code intact or transferring files in an environment without a network connection. It's not suitable for daily multi-person collaboration; that's for remote work. The water pipe is the right place for that.

There are still many unexplored doors in Git. Bundle is the most worthwhile door I opened because it gave me a complete sense of control over "how the code gets from here to there" for the first time.

A little background on Git

Git was written by Linus Torvalds in about two weeks in 2005. The reason was that the version control tool originally used by the Linux kernel suddenly became unavailable for free. He needed something urgently, so he wrote one himself. The lesson of this story isn't "You can also write Git in two weeks," but rather "Sometimes the best tools come from a very specific, very urgent need."

Git was designed from the outset to be distributed: each clone is a complete repository with its entire history. Remotes are merely address books indicating "where another clone resides," not essential components of Git. Many people have used Git for years without realizing this because GitHub is so convenient that they assume Git is unusable without it. Bundles are the most direct manifestation of this "no need for remotes" philosophy.

git bundleAdded in 2007, one of its initial uses was in environments completely without internet access: military, financial, and certain research institutions, where code can only be accessed via physical media. It's not some obscure hack; it's a scenario Git considered from the beginning, just one that most people don't need in their daily lives.

Three common misunderstandings

The first misconception is that "a bundle must encompass the entire history." No, it doesn't.git bundle create fix.bundle main ^origin/main Packing only the part that the other party doesn't yet provide will significantly reduce the file size. Providing a complete history is my own choice, because I want each bundle to be verifiable independently, without relying on what the other party has.

The second misconception is that "verifying guarantees fetching." Verifying checks if the bundle is complete and if I have the necessary prerequisite commits; fetching...--ff-only The real question is whether this history makes sense. Both must pass for it to count. I've encountered situations where only the first one passes; in those cases, the other party's main entry had a commit I didn't have.

The third misconception is "If you use bundles, you don't need remotes anymore." For everyday collaboration, use remotes. Bundles are for specific situations: production machines, offline environments, and archives. If you insist on using bundles for multi-person collaboration, you'll quickly become the person everyone's waiting for.

A small exercise you can try over the weekend

Find a small project of your own.git bundle create ~/test.bundle main Copy that file to another folder.git clone ~/test.bundle 新專案 Yes, you can clone the bundle directly. Go in and take a look.git log The entire history is there. Then go back to the original project, modify a file, commit, create a bundle, and add it to the new project.git bundle verify Thengit fetch addmerge --ff-only .

Finally, I did one bad thing: I committed something to the new project, then fetched a bundle from the original project, and then...--ff-only It will reject you because the histories of the two sides have diverged. That rejection message is the one I most want to see on the official server.

Postscript

My fondness for Bundle is partly irrational. It makes me feel that code is something tangible, something I can hold in my hand, rather than an invisible stream of water. This feeling is a bit outdated in an era where everything is in the cloud, but sometimes outdated things are just right.

Official website: git-scm.com/docs/git-bundle

Reading preferences

Back to all postsAbout the author and corrections (Chinese)