I seem to be having an issue with a repository continually recreating branches locally because of some branches on remote. I'm on a Windows machine, so I suspect that it's a case sensitivity issue.
Here's an example couple commands:
$ git pull
From https://github.com/{my-repo}
* [new branch] Abc -> origin/Abc
* [new branch] Def -> origin/Def
Already up to date.
$ git pull -p
From https://github.com/{my-repo}
- [deleted] (none) -> origin/abc
- [deleted] (none) -> origin/def
* [new branch] Abc -> origin/Abc
* [new branch] Def -> origin/Def
Already up to date.
When doing a git pull
, the branches in question are capitalized. When I do a git pull -p
(for pruning), it first tries to delete lowercased versions of the branches, then create capitalized versions.
The remote branches are capitalized (origin/Abc
and origin/Def
).
I have tried to temporarily change my Git config such that ignorecase=false
(it is currently ignorecase=true
). But I noticed no change in behavior. I'm guessing there's something local on my end that's currently holding onto those lowercased branches. But git branch
does not show any version of these branches locally.
Short of completely obliterating the repository (a fresh git clone
in a separate folder does not pull these phantom branches when trying pulls/fetches), is there anything I can do?
Git is case sensitive. It is completely valid but goofy to have 2 branches with different cases like dev and DEV.
Setting a Branch Permission Pattern is case sensitive and doesn't match git's branch name behavior. It should be case insensitive. Git branches are not case sensitive. So if you have a branch abc123 locally and you push, it can match up to ABC123 on the remote server.
Git Branch Rename Command The steps to change a git branch name are: Rename the Git branch locally with the git branch -m new-branch-name command. Push the new branch to your GitHub or GitLab repo. Delete the branch with the old name from your remote repo.
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 your Abc
from your abc
, 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
and Abc
if one or both might be messed up. Your remember-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 named Abc
and abc
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 calls origin
—has no bad names, and your local Git has made your local origin/*
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 remember abc
, you can just run git branch -m remember-abc abc
to move remember-abc
to abc
.
If abc
should have origin/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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With