The short answer is no: you will have to get used to this behavior. (It may help to know just what Git is doing and why.)
As Jeff Mercado mentioned in a comment, you can get git diff
to tell you what a future git switch
will do. Note that git diff
's main job here is to compare two commits or compare one commit to the work-tree.1 In other words, we extract the first commit into a temporary area, then extract the second commit into a temporary area—or use the work-tree instead of the second commit—and compare them, with the first commit on the "left side" of the comparison and the second on the "right side"
If you just run git diff commit-specifier
with no options at all, Git does such a comparison, then gives you a recipe to follow: add this new file, delete this file entirely, delete these lines from file A, add these lines to file B and so on. Following this recipe will change the left-side commit to the right-side one (or to match the work-tree).
If you add the --name-only
option, Git tells you less: instead of giving you the recipe, it just lists the names of files that would be added, removed, or modified.
If you use instead the --name-status
option, Git tells you an in-between amount: it still lists the names of the files, but this time it prefixes each one with a letter code: A
for add this file, D
for delete this file, and M
for modify this file in place. (There are some more letter codes; see the documentation for all the details.)
Hence:
git diff otherbranch
gives you a recipe for taking the snapshot represented by the last commit of the branch named otherbranch
, and turning that snapshot into what's in your work-tree right now. So:
git diff --name-status otherbranch
will tell you what files would have to be added to that commit to produce what you have in your work-tree: such files will, in general, be removed when you switch to that commit. It will tell you what files would have to be deleted from that commit to produce what you have in your work-tree: such files will, in general, be added when you switch to that commit. And, it will tell you which files would have to be modified. If you had Git show you what changes would have to be made, then git checkout otherbranch
will, in effect, make the opposite changes.
1The git diff
command actually has a number of main jobs, and I'm skipping over the one that you get if you run git diff
with no arguments at all. That compares the contents of Git's index to the contents of the work-tree. I'm trying to keep this answer free of information about Git's index, except in these footnotes.
Why is this all backwards?
The reason the above seems backwards is that this isn't really how this is intended to be used.
Remember, every commit holds a full and complete snapshot of every file. When you make a Git commit, Git saves—forever2—a frozen, compressed, read-only, Git-only snapshot of every file you told Git about.3 These frozen and Git-ified copies of the files can never be changed, and will live as long as the commit does (see footnote 2).
But only Git can use these files. That means these commits are fine as archives, but useless for getting any actual work done. To use a commit, you have to get Git to extract it, turning all the frozen, Git-ified files into ordinary everyday files that you can see and work with.
Those ordinary files go into your work-tree. These are the files you can open in your editor. Hence the whole point of git switch
(or in older versions of Git, git checkout
) is to replace the entire work-tree with one from some other commit. Since every commit has a complete snapshot of all files, it only makes sense to remove this one, and check out / switch to a different one.
So this kind of git diff
is perhaps most useful when run just as:
git diff HEAD
which compares what's in the current commit—the one you selected earlier with git switch
—to what's in the work-tree right now. Any differences are things that you can git add
and then git commit
: your new commit, once you've set it all up with git add
, will contain the new snapshot based on the files you copied into the index and then committed. ... Oops, I've gone and mentioned the index in the body of the answer.
2A commit really only sticks around as long as you can find it. Each commit has a unique hash ID, and Git has ways to find these hash IDs so that you—and Git—can find the commits. If you really need to get rid of a commit, it tends to be hard, but you can force Git to "forget" a commit as long as you are willing to forget all of its children as well. In this case, the commit will eventually be removed via Git's garbage collector, git gc
.
3Technically, Git makes each new commit from the copies of files that appear in Git's index. You use git add
to copy a file from your work-tree into Git's index. When you switch to a commit, Git fills its index from that commit, and then fills your work-tree from what's now in the index.
A brief overview of the index
The index—this phrasing implies that there's only one, though in fact there's one per work-tree, which matters if you start using git worktree
—has a lot of roles in Git, and hence has three different names that all mean the same thing. It's often called the staging area, and occasionally—rarely these days—the cache. It does a lot of things but its biggest, main function is that it is where you build the next commit to make.
The index contains a copy of each file that will go into the next commit.5 As mentioned in footnote 3, switching to a commit copies that commit's files out, to both the index and your work-tree. The index's copy of each file is in the frozen Git-only internal format, ready to go into the next commit. The work-tree copy is the one that's useful to you, which is why Git writes it into an area that's mainly for your use.
You can change the work-tree copy all you want. That has no effect on the index copy. So, if you have changed the work-tree copy, and you'd like the updated file to be in the next commit, you now have to run:
git add path/to/file
to tell Git: Copy the work-tree copy back into the index, replacing the previous copy there. Git will squash the file down into the frozen format, ready to go into the next commit.
When you do run git commit
, Git just packages up whatever is in the index at that time and makes that the snapshot for the new commit. That's why you have to git add
so often: you need to replace the index copy, now that you've changed the work-tree copy.
You can't change the HEAD
copy: that's inside a commit, and all commits are frozen for all time. You can, of course, make a new commit, after which Git automatically switches to the new commit—so after committing, HEAD
identifies the new commit, and the new commit's frozen-for-all-time copy matches the index's frozen (but replaceable) copy. Note how the work-tree copy was irrelevant to this process.
(Git doesn't write to your work-tree copy in this automatic-switch-to-new-commit as there's no real actual switching happening. For way too much on this, see Checkout another branch when there are uncommitted changes on the current branch.)
5Technically, the index contains a reference to a copy of the file's content, in the frozen format.
Untracked files
It's worth mentioning one last wrinkle. In all of the above, your work-tree is mostly yours, to do with as you will, except for the fact that git checkout
or git switch
has to fill it in from a commit—which of course replaces all your files.
But since your work-tree is yours, you can create files in it that are not now in Git's index. You can also use git add
or git rm
to copy files into the index that were never there before, or remove files from the index that were there (as the result of a switch-or-checkout) so that they're not in the index any more. Either way, it's pretty easy to see that you can create a file in your work-tree that is not in Git's index.
A file that isn't in the index right now is an untracked file. That's actually pretty simple. A file is untracked if it's not in the index. (It needs to be in your work-tree of course. If it's not in your work-tree either, it's just not there at all, so why are we talking about it?) Since git add
puts files into the index, and git rm
takes them out,6 you can change a file's status from untracked to tracked, or vice versa. This never change any existing commit: the files stored inside a commit are frozen that way for all time.
Tracked or untracked is a property of the index, which is temporary in the same way your work-tree is temporary. Committed is permanent, as long as the commit itself continues to exist. The purpose of git switch
is to select a commit and fill in the index-and-work-tree from that commit. Git will generally avoid clobbering untracked files you've put into your work-tree, but think of your work-tree as temporary, because it is: it's the commits that matter, in the end.
6Oddly, git add
can remove an index copy of a file. Suppose that path/to/file
is in the index right now, and not in your work-tree for whatever reason (you, or something you ran removed it, probably). If you now run git add path/to/file
, Git will remove the copy from the index. This is because git add
really means make index match work-tree, not just copy from work-tree to index.