You cannot do this except when you are forced to do this.
This answer above makes no sense until you understand that Git isn't really about branches at all. Git is all about commits.
Each commit has its own big ugly hash ID, such as 08da6496b61341ec45eac36afcc8f94242763468
. This ID means this commit, and no other commit. Try:
git rev-parse HEAD
in your own Git repository, to see the hash ID of your current commit. That hash ID now means that commit, forever more, and never any other commit.1
But commit hash IDs are big and ugly and no mere human is going to remember them. We (humans) need a device that will remember them for us. Perhaps we can use our computers! They're good at remembering long strings like that. So what we do is have Git remember these hash IDs for us.
There are several different places that Git will remember hash IDs for us. For instance, tag names hold one commit hash ID.2 But in fact, branch names, like master
, also just hold one commit hash ID.
Fortunately, each commit holds hash IDs as well! This is how what Git forms what we tend to think of as branches, although we (humans) also tend to think of our branch names, and sometimes some of our other names in our Git repositories, as "branches" as well. For more on this, see What exactly do we mean by "branch"?
Each commit stores a full and complete snapshot of all of our files. A commit does not store changes! It stores whole files, intact.3 So if commit A
(where A
stands in for some hash ID) stores the content of your file foo/bar/file.txt
, commit A
has a full and complete copy of the file—not differences, just a copy. If some other commit B
stores the content of your file foo/bar/file.txt
, commit B
also has a full and complete copy.
These full and complete copies are automatically shared if they match exactly. Every commit, once made, is completely and totally read-only, frozen for all time.4 That includes the saved file contents as well. So if commit A
stores the same content—even just for one file—as commit B
, the underlying file storage for that file is shared. But you cannot change that copy of the file. If you do change the file, you must make a new commit C
, which gets its own new and unique hash ID. This new commit C
can have a file foo/bar/file.txt
but, since we said you changed that file's content (to something that does not match any other existing commit), that file's content is new to commit C
. It can be shared again with later commits D
, E
, and F
as long as foo/bar/file.txt
continues to have the same content.
1Technically, the hash ID is simply a cryptographic checksum of the entire contents of the commit object. In theory, some different object could have the same checksum. However, if you already have an object with hash H, your Git will not allow any new-but-different object with the same hash to enter your repository. So once an object ID is "taken", it means that object from then on.
See also How does the newly found SHA-1 collision affect Git?
2A lightweight tag holds the hash ID directly, while an annotated tag holds it through an annotated tag object, which also holds additional data. This tag object gets its own unique hash ID and the tag name then holds the tag object's ID. Git will use the name to find the tag object, and then read the tag object to find the stored hash ID.
Tags can have as their tag-target any of Git's four internal object types. If an annotated tag points to another annotated tag, Git will keep following the tag until it reaches some other object type, so ultimately, each tag points to some non-annotated-tag object, but possibly after many indirections. If the ultimate object is a tree or blob object, the tag doesn't point to a commit after all.
3At the level of a commit, Git stores one tree object. The tree object then refers to blob objects and more tree objects. The tree objects hold file name components and references to blob hash IDs, and the blob objects hold the files' contents.
These objects are initially stored as loose objects, which are always whole and intact. Later, Git eventually compresses the objects into pack files. When stored in a pack file, an object can be delta compressed against other packed objects. So, at some point, "files"—really, objects—do get stored via delta encoding, which is similar to storing diffs. But that happens later, and is not visible from the level at which Git deals with files. Note that the tree and commit objects themselves are, or at least can be, delta-compressed as well!
4This is necessary, as well as natural, because of the way Git stores objects. Each object has its content, whatever that is, and then hash a hash ID that is computed by this same checksum process mentioned in footnote 1. If you take an object out of the database, modify even a single bit, and write that back to the database, the new object has a new and different checksum. If the new data are already in the database, the new object isn't new after all: Git just re-uses the existing object. Otherwise Git writes that object into the database, and now that hash ID is taken.
There are procedures for releasing ("garbage collecting") unused objects, if and when they arise (which actually is relatively often in Git—many commands make temporary objects, then throw them away). You don't have to do anything to have this happen: Git itself runs git gc --auto
for you when it's appropriate. But hash IDs have enough bits that an accidental collision never happens in practice; only deliberate, forced collisions can happen. Again, see How does the newly found SHA-1 collision affect Git?
But the above is all about commits! What about branches?
We just said that if commits A
and B
have the same file-content for foo/bar/file.txt
, they actually share the underlying copy of the file. In Git's terminology, they share the blob object. But commit C
introduced a new and different version of foo/bar/file.txt
, so commit C
has a new and different blob object. Commits D
, E
, and F
, then re-use that content for their foo/bar/file.txt
files, because you made commits D
, E
, and F
without changing the file.
Now, an interesting thing about commits is that besides storing files—a snapshot of all of your files—each commit also stores some metadata. In the metadata, you will find your own name and email address, stored as "author" and "committer". You will find two date-and-time-stamps, indicating when you made the commit.5 Git stores your log message: subject and body text for git log
, to reminds yourself and others why you made this commit. And, perhaps most important of all, you will find the hash ID of a parent commit, or sometimes some number of parent commits. Usually there is just the one parent.
Remember, this parent is the raw hash ID of a commit—a big ugly string of letters and numbers. That's Git remembering the hash ID for us, so that we don't have to.
Whenever something in Git is holding a big ugly hash ID, we say that that thing points to the corresponding object. So if a commit holds the hash ID of its predecessor (parent) commit, the comimt points to its parent. That means that commit B
points back to commit A
, and commit C
points back to commit B
, and so on:
A <-B <-C ... <-F
These backwards-pointing commit arrows, embedded within each commit, form a chain that links the commits together. Since nothing about any commit can ever change, we can draw the chain like this:
A--B--C--D--E--F
But where do the branch names come in? The answer is: right here. The last commit in this chain is commit F
(whatever its real, big and ugly, hash ID really is—we're just calling it F
here). To remember this hash ID, Git gives us a branch name. Let's say this is branch master
. The name master
will then hold the actual hash ID of commit F
:
A--B--C--D--E--F <-- master
Now let's create a new branch dev
. Which hash ID does dev
hold? Well, let's have it hold F
too! So now we have:
A--B--C--D--E--F <-- master, dev
We pick one of these two branches to be "on":
git checkout dev
and then we do some work and make a new commit, which gets its own new, unique, big and ugly hash ID. We'll call this hash ID G
. Commit G
will point back to existing commit F
, since that's the commit we checked out in order to make G
, and we get:
A--B--C--D--E--F
\
G
Where do the branch names point? Well, master
still points to commit F
—but because we just made new commit G
, Git automatically updates the branch name to point to new commit G
, giving:
A--B--C--D--E--F <-- master
\
G <-- dev (HEAD)
We draw the word (HEAD)
next to the branch name that we used with git checkout
, so that we—and Git—know which branch name gets updated as we make new commits.
If we make more new commits on dev
, they point back to the existing commits on dev
. Existing commit G
points back to commit F
. Which branch is commit F
on? Git's answer is unusual: commit F
is on both branches at the same time.
But, what if we git checkout master
, so that we have:
A--B--C--D--E--F <-- master (HEAD)
\
G <-- dev
and then make a new commit H
? Well, that works just like last time. The parent of new commit H
is the commit we have checked out a moment earlier, which is commit F
, and our name moves to point to the new commit:
H <-- master (HEAD)
/
A--B--C--D--E--F
\
G <-- dev
Commits A
through F
are on both branches. Commit H
is only on master
, and commit G
is only on dev
.
5The reason for the two time stamps is that you can copy a commit, while (presumably) changing something about it. The new commit gets a new committer line: you made the new commit right now. But it retains its old author line: whoever wrote the original commit, whenever they wrote it, is captured in the new commit too. So every commit has an author: the person who wrote the original commit, with name and email address and time stamp. Every commit has a committer too: the person who made that particular commit, with name and email address and time stamp. If the two match, this is the original copy of the original commit.
Now we can see why your original question is answered mostly "no" in Git
We start with a file named config
that exists in snapshots G
and H
at this time:
H <-- master (HEAD)
/
A--B--C--D--E--F
\
G <-- dev
with the same content. So this file is shared—stored just once—and both H
and G
use one shared blob object to store it. But then you modify the work-tree copy of the file, git add
, and git commit
to make new commit I
:
H--I <-- master (HEAD)
/
A--B--C--D--E--F
\
G <-- dev
Commit I
has a new, different copy of config
: a new blob. You'd like for branch dev
to automatically acquire a new commit:
H--I <-- master (HEAD)
/
A--B--C--D--E--F
\
G--J <-- dev
where commit J
is the same as commit G
except that the file config
has been updated.
You can do that manually, by doing git checkout dev
and then making a new commit J
on dev
that has, in its snapshot, the same updated copy of config
. But Git cannot do that automatically.