"error: Your local changes to the following files would be overwritten by checkout" on file outside of my repository - what can I do?

Viewed 52

I have a github repo that I somehow horked up from Mac that I can no longer switch branches in.

My .NET 6.0 ASP.NET web site project uses NLog, which is a logging package configured by nlog.config XML file:

<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      autoReload="true"
      internalLogLevel="Info"
      internalLogFile="c:\temp\internal-nlog-AspNetCore.txt">

The internalLogFile property where NLogs internal logging goes.

On Mac, the repo is in /Users/dodievich/mycompany/myprojectname directory.

Few months ago I loaded and ran this project from Mac, which caused NLog to create /Users/dodievich/mycompany/myprojectname/c:\temp\internal-nlog-AspNetCore.txt file where c:\temp\internal-nlog-AspNetCore.txt is the full file name, not the file path (backslashes are part of filename on the Mac):

> ls *og*
-rw-r--r--  1 dodievich  staff  3889 Jul 12 18:29 NLog.config
-rw-r--r--  1 dodievich  staff  1611 Jul 12 18:29 c:\temp\internal-nlog-AspNetCore.txt

For bonus unhappy path stuff, pwsh on Mac goes a bit nuts (but that's just to indicate that mixing windows and mac paths between OSs is evil):

PS /Users/dodievich/mycompany/myprojectname> Get-ChildItem *log*

    Directory: /Users/dodievich/mycompany/myprojectname

UnixMode   User             Group                 LastWriteTime           Size Name
--------   ----             -----                 -------------           ---- ----
drwxr-xr-x dodievich        staff               7/12/2022 18:22             96 logs
Get-ChildItem: Could not find item /Users/dodievich/mycompany/myprojectname/c:/temp/internal-nlog-AspNetCore.txt.
-rw-r--r-- dodievich        staff               7/12/2022 18:29           3889 NLog.config

When that happened, I added .DS_Store and c:\temp\internal-nlog-AspNetCore.txt files to .gitignore to ignore. I didn't think twice to check that in from my Mac:

> git diff <commit n> <commit n+1>
diff --git a/.gitignore b/.gitignore
index e06b49c..0bd5af8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -332,3 +332,5 @@ ASALocalRun/

 # Local History for Visual Studio
 .localhistory/
+c:\temp\internal-nlog-AspNetCore.txt
+.DS_Store

Meanwhile, on Windows VM, the repo is in C:\mycompanyname\myprojectname directory.

I usually work in main branch. Recently, another developer created anotherbranch branch which I am unable to switch to on my Windows VM. I receive this error:

> git checkout -q --track origin/anotherbranch
error: The following untracked working tree files would be overwritten by checkout:
        c:\temp\internal-nlog-AspNetCore.txt
Please move or remove them before you switch branches.
Aborting

If I delete c:\temp\internal-nlog-AspNetCore.txt, the error changes to:

> git checkout -q --track origin/anotherbranch
error: invalid path 'c:\temp\internal-nlog-AspNetCore.txt'

As you can see from the path the c:\temp\internal-nlog-AspNetCore.txt is outside of where the repo is at in C:\mycompanyname\myprojectname.

I'd really like to be able to switch to another branch. I think my adding this file to .gitignore made it track somehow? No combination of git clean, git reset, git rm or removal of this file from .gitignore seems to be able to make this error go away.

What can I do?

2 Answers

As you've just seen, these kinds of "funny" paths—which work just fine on Linux and macOS in general—create all kinds of headaches for Windows users. (Before the macOS users point and laugh, we might note that storing two different files, one named s c h o combining-umlaut ¨ n, and one named s c h ö n, on Linux, works just fine and makes two separate files, which then collide on macOS, in a similar but different File-Name-Circus-from-Hell.)

When that happened, I added .DS_Store and c:\temp\internal-nlog-AspNetCore.txt files to .gitignore to ignore ...

> git diff <commit n> <commit n+1>
diff --git a/.gitignore b/.gitignore
index e06b49c..0bd5af8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -332,3 +332,5 @@ ASALocalRun/

 # Local History for Visual Studio
 .localhistory/
+c:\temp\internal-nlog-AspNetCore.txt
+.DS_Store

Note that the diff from the old commit to the new commit is, here, displayed as just that one change to .gitignore. This means (assuming you have not over-snipped, as it were) that you did not delete the .DS_Store and c:\temp\internal-nlog-AspNetCore.txt from Git's index / staging-area, which means that those two files were in the new commit, and presumably are still in every commit made since then as well.

Placing a file into .gitignore does not cause the file to go away. Moreover, existing commits cannot be changed: they contain a snapshot that has the file. They will always contain that snapshot. You can make new-and-improved commits that are very much like the originals, but are improved because the unwanted files aren't in the improved commits, but those will be new and different commits with new and different hash IDs.1

Generally, though, the thing to do is to remove the bad files now, commit, and make sure all users of all clones of this repository are aware of what's just happened and that they likewise clean up anything they're doing that needs to be cleaned up.

The fixing-up is easiest to do on a machine on which the problem itself doesn't occur: in this case, a Mac or Linux box. There's no theoretical issue with doing this on a Windows box, but working with commits without actually checking them out is tricky and somewhat painful. There are too many traps for the unwary here.

On the macOS system, all you will need to do is:

  • clone the repository if necessary;
  • check out the right branch;
  • remove the "bad" files with git rm, which should be easy enough (although you might have to use sh/bash/zsh instead of PowerShell; remember also that backslashes in sh/bash require quoting so you have to type in git rm 'c:\temp\internal-nlong-AspNetCore.txt');
  • commit the removal, and git push the result

and now you have a branch whose tip-most commit doesn't contain the badly named files, so you can now git fetch that commit to your Windows VM (and thus update the appropriate remote-tracking name) and then just use it.


1If you re-do every commit and make the branch name(s) you use find only the new commits, we call that "rewriting history". It's bad in that everyone, and I mean everyone, who has the old repository—and any ongoing work using the old repository—has to have a big Flag Day where they change over to using the new repository, and they have to check all their commits for the same kind of error and fix them up themselves. It's not hard, but it is tedious. On the other hand, once done, the problem doesn't recur, unless someone reintroduces all the bad commits via a careless git pull or something. Would that ever happen?2

2Of course it would. I speak as the guy who, sometime before 2010 or so, resorted to writing a pre-receive hook to reject particular commits by raw hash ID.

In case you were not able to switch branch (because of a trouvlesome file), you can also use git filter-repo to filter out said file:

git filter-repo --path-glob '*/temp/internal-nlog-AspNetCore.txt' --invert-paths

However, that would rewrite any commit where that file was, which would involve git push --force.

If a git rm in the right branch is enough, this is safer.

Related