GitHub Branches: Case-Sensitivity Issue?
On Git, branches are just pointers to a commit. The branches are stores as plain files on your .git
repository.
For instance you may have abc
and def
files on .git/refs/heads
.
$ tree .git/refs/heads/
.git/refs/heads/
├── abc
├── def
└── master
The content of these files is just the commit number on which the branch is pointing.
I am not sure, but I think the option ignorecase
is only relevant to your working directory, not the .git
folder. So to remove the weird capitalized branches, you may just need to remove/rename the files in .git/refs/heads
.
In addition to this, the upstream link from a local branch to a remote branch is stored on the .git/config
file. In this file you may have something like:
[branch "Abc"]
remote = origin
merge = refs/heads/abc
Notice in this example that the remote branch is named Abc
but the local branch is abc
(lowercase).
To solve your issue I would try to:
- Modify the
.git/config
file - Rename the corrupted branches in
.git/refs/heads
such asabc
is renamedabc-old
- Try your
git pull
The answers supplied by nowox and torek were very helpful, but did not contain the exact solution. The existing references to remote in .git/config
, and the files in git/refs/heads
did not contain any versions of abc
or def
.
Instead, the problem existed in .git/refs/remotes/origin
.
My .git/refs/remotes/origin
directory had references to the lowercased versions of these feature branch folders. Some feature branches were made under abc
and def
using the lowercased versions, but they no longer exist on remote. The creator of these feature branches recently switched to using Abc
and Def
on remote. I deleted .git/refs/remotes/origin/abc
and .git/refs/remotes/origin/def
then executed fresh git pull -p
commands. New folders, Abc
and Def
, were created, and subsequent pull
s or fetch
es correctly display Already up to date.
Thanks to nowox and torek for getting me on the right track!
Git is schizophrenic about this.1 Parts of Git are case-sensitive, so that branch HELLO
and branch hello
are different branches. Other parts of Git are, on Windows and MacOS anyway, case-insensitive, so that branch HELLO
and branch hello
are the same branch.
The result is confusion. The situation is best simply avoided entirely.
To correct the problem:
Set some additional, private and temporary, branch or tag name(s) that you won't find confusing, to remember any commit hash IDs you really care about, in your own local repository. Then run
git pack-refs --all
so that all your references are packed. This removes all the file names, putting all your references into the.git/packed-refs
flat-file, where their names are case-sensitive. Your Git can now tell yourAbc
from yourabc
, if you have both.Now that your repository is de-confused, delete any bad branch names. Your temporary names hold the values you want to remember. You can delete both
abc
andAbc
if one or both might be messed up. Yourremember-abc
has the correct hash in it.Go to the Linux server machine that has the branches that differ only in case from yours. (It's always a Linux machine; this problem never occurs on Windows or MacOS servers because they do the case-folding early enough that you never create the problem in the first place.) There, rename or delete the offending bad names.
The Linux machine has no issues with case—branches whose name differs only in case are always different—so there is no weirdness here. It may take a few steps, and a few
git branch
commands to list all the names, but eventually, you'll have nothing but clear and distinct names: there will be no branches namedAbc
andabc
both.If there are no such problems on the Linux server, step 2 is "do nothing".
Use
git fetch --prune
on your local system. You now no longer have any bad names as remote-tracking names, because in step 2, you made sure that the server—the system your local Git callsorigin
—has no bad names, and your local Git has made your localorigin/*
names match their branch names.Now re-create any branch names you want locally, and/or rename the temporary names you made in step 1. For instance if you made
remember-abc
to rememberabc
, you can just rungit branch -m remember-abc abc
to moveremember-abc
toabc
.If
abc
should haveorigin/abc
set as its upstream, do that now:git branch --set-upstream-to=origin/abc abc
(You can do this in step 1 when you create
remember-abc
, but I think it makes more sense here so I put it in step 4.)
There are various shortcuts you can use, instead of the 4 steps above. I listed all four this way for clarity of purpose: it should be obvious to you what each step is intended to accomplish and, if you read the rest of this, why you are doing that step.
The reason the problem occurs is outlined in nowox's answer: Git sometimes store the branch name in a file name, and sometimes stores it as a string in a data file. Since Windows (and MacOS) tends to use file-name-conflation, the file-name variant retains its original case, but ignores attempts to create a second file of the other case-variant name, and then Git thinks that Abc
and abc
are otherwise the same. The data-in-a-file variant retains the case-distinction as well as the value-distinction and believes that Abc
and abc
are two different branches that identify two different commits.
When git rev-parse refs/heads/abc
or git rev-parse refs/remotes/origin/abc
gets its information from .git/packed-refs
—a data file containing strings—it gets the "right" information. But when it gets its information from the file system, an attempt to open .git/refs/heads/abc
or .git/refs/remotes/origin/abc
actually opens .git/refs/heads/Abc
(if that file exists right now) or the similarly-named remote-tracking variant (if that file exists), and Git gets the "wrong" information.
Setting core.ignorecase
(to anything) does not help at all as this affects only the way that Git deals with case-folding in the work-tree. Files inside Git's internal databases are not affected in any way.
This whole problem would never come up if, e.g., Git used a real database to store its <reference-name, hash-ID> table. Using individual files works fine on Linux. It does not work fine on Windows and MacOS, not this way anyway. Using individual files could work there if Git didn't store them in files with readable names—for instance, instead of refs/heads/master
, perhaps Git could use a file named refs/heads/6d6173746572
, though that halves the available component-name length. (Exercise: how is 0x6d m
, 0x61 a
, and so on?)
1Technically, this is the wrong word. It's sure descriptive though. A better word might be schizoid, as used in the title of one episode of The Prisoner, but it too has the wrong meaning. The root word here is really schism, meaning split and somewhat self-opposed, and that's what we're driving at here.