I've been working on a repository for over a year with no issues. Someone else committed to it for the first time, and when I tried to pull their changes, I got this message:
error: The following untracked working tree files would be overwritten by merge:
.DS_Store
I can't find a file called .DS_Store
anywhere in my repository. I added .DS_Store
to my .gitignore
and committed those changes, but I'm still getting the error. Then I tried running git stash
before git pull
but that also didn't help. How do I fix it?
Working in RStudio on a Macbook Pro.
Someone else committed to it for the first time ...
"Well, there's your problem!" 😀
Seriously, what happened is that this someone else put their .DS_Store
file into their commit. You accepted their commit, so you accepted the idea that .DS_Store
should be stored in your Git repository, in that commit. (You might consider ejecting this commit from every copy of the repository that you control, and making them re-do it correctly, or you might just fix it for them in every repository that you control by extracting their commit somewhere safe, removing the file, and making a new additional commit. Note that you must then carefully avoid ever checking out their commit anywhere this would be "unsafe".)
As SwissCodeMen said, .DS_Store
is a hidden file that the macOS Finder creates and uses to store information about where and how to display various icons for various files and sub-directories within a directory (or folder if you prefer that term). Yours records what you want shown and where; theirs records what they want shown and where; and you probably don't want theirs and they probably don't want yours. However, this sort of thing is pretty harmless when overwritten: you can just remove it and let Finder create one from scratch. You'll lose any careful re-organization you did to put your icons where you wanted them, though.
Every commit in every Git repository stores every file. There are a bunch of things to know about this, depending on what level of Git mastery you want. The minimum set you need to know just to use Git is this:
A repository is, at its heart, a collection of commits. A Git repository is not about files, though commits contain files. A repository is not about branches, though commits are arranged in things we call branches, and different things that we also call branches—or more precisely, branch names—help us (and Git) find the commits. But in the end the repository is about the commits.
Each commit has a unique, but random-looking and impossible to remember, "number", expressed in hexadecimal. This number is unique to that particular commit: no other Git repository—anywhere, for any purpose—may use that number unless it's to store that one particular commit.1 This is how two different Git clones, upon meeting each other, decide which commits one has that the other lacks.
Each commit stores two things:
A commit stores a snapshot of all files, as in this section. The files inside the commit are stored in a special way though: they're compressed—sometimes very much so—and Git-ified, and de-duplicated. If the same file is in millions of commits, it's only stored once. So even though Git stores every version of every file, each version only has one copy, and sometimes that one copy is reduced to just a few bytes.
Note that, although it sounds funny when put this way, a commit stores only the files that it stores. That is, commit A
(A
standing in for some hash ID) might be your very first commit, where you had just a README.md
file or some such. So commit A
has only that one file, or that file plus a few other skeleton startup files. The next commit B
might have more files in it, but A
only has the initial files.
A commit stores some metadata, or information about the commit itself. That includes stuff like who made it, when, and why (their log message). The metadata string commits together, so that Git can find one commit from another commit.
Every commit, once made, is completely read-only. (This is actually true of all of Git's internal objects and is due to the hash-ID-based storage trick.)
Because a commit is read-only, and has Git-ified files that only Git can read and literally nothing—not even Git itself—can overwrite, you must extract a commit before you can work on or with it. The extracted, to-be-worked-on copy is not the commit itself: it's a copy. This copy goes in your working tree, where you do your work.
When you go to extract a copy of a commit, Git will, in general, overwrite your previous working tree copy. That is, Git has to throw out all the old files from the previously selected commit, and replace them with the new copies from the newly selected commit.
For some files—your untracked files, whether they're ignored or not—Git can just leave the files alone. That's great for you, most of the time: it means your macOS Finder can drop various .DS_Store
files all over your working tree, and as long as you don't add and commit the files so that they never go into any commit in your repository, Git never disturbs these working-tree files.
But some files are in commits. Here, if some files F1 and F2 are in current commit C
, and you're switching to new target commit T
that has the same copy of F1 but a different copy of F2, Git is going to have to rip file F2 out of your working tree and put in the T
version of F2.
The same goes for a .DS_Store
file: if your co-worker / colleague / someone else made a commit that holds some .DS_Store
file, and your current commit C
doesn't have a .DS_Store
file but your working tree does have one, you have an untracked .DS_Store
file. Note that it does not matter if this file is ignored or not; what matters is that you have it in your working tree, and there's a copy of a file named .DS_Store
in the target commit T
too. You've told Git to switch to commit T
, from current commit C
, so Git has to remove your (untracked) .DS_Store
and put in their .DS_Store
from T
.
Git—rightly!—complains that it will overwrite, i.e., discard, your .DS_Store
file. And it will, if you check out commit T
. Your options are:2
T
, overwriting your .DS_Store
file anyway;.DS_Store
first so that overwriting is no longer a problem, then check out commit T
; orT
.Note that you cannot fix commit T
because no commit can ever be changed. You can make a new and improved version of T
by checking it out, removing the .DS_Store
file, and making a new commit T2
, but when you do that, your existing .DS_Store
file will be overwritten briefly until you remove it. (You can then put your untracked .DS_Store
file back from wherever you saved it, if you like.)
If saving and restoring your .DS_Store
file is particularly painful for some reason—it probably isn't, but there might be other files for which this might be the case—consider using git worktree add
to add a temporary working tree and branch, in which you do the fixup commit to make corrected commit T2
, without touching your existing working tree. Do this with the command line, without using any fancy GUI including the macOS Finder, so that Finder won't use the .DS_Store
file in the temporary added working tree. This add-a-working-tree technique lets you use all the usual everyday Git tools without having to learn a lot of deeper Git commands.
1This technical requirement for uniqueness is technically impossible—provably so, via the pigeonhole principle—but the huge size of a commit hash ID helps put off the inevitable disaster; we hope that it doesn't happen until after the universe ends, or at least we're all dead and don't care. If and when it does happen, the universe is destroyed er no the Rapture occurs uh wait that's not right we all turn into frogs? ah yes: the two Git repositories in which there's a hash collision of this sort can't talk to each other any more. So, not really all that much of a disaster, just pretty annoying.
2Technically, there are several more options. For instance, you could use:
T2
; orgit read-tree
, git rm --cached
, git write-tree
, and git commit-tree
to literally avoid checking out commit T
while making commit T2
.These are pretty tricky to explain and it's better for most people to just use regular Git operations.