How to Prevent Git from Committing Two Files with Names Differing Only in Case

How to prevent git from committing two files with names differing only in case?

There's nothing built in (although there should be, no doubt). What you can do is provide a pre-commit hook that verifies that all names are OK and prevents the commit if not.

This hook only needs to be run on the Linux box (although making it work on Linux and Mac is easy, it's just Windows with its default impoverished toolbox that is problematic). You might want to add it to a side branch and give the Linux folks instructions on setting it up.

You may want to check branch names as well, as in git pre-commit or update hook for stopping commit with branch names having Case Insensitive match. (Interesting: the answer on this question is my own; I had forgotten it.)

First, let's write a "check for case conflict" function. This is just a matter of sorting with case-folding (so that "helloworld" and "helloWorld" are placed adjacent to each other), then using uniq -di to print any duplicate (after case-folding) strings, but no non-duplicates:

sort -f | uniq -di

If this produces any output, these are the "bad names". Let's capture the output in a temporary file and check its size, so we can print them to standard output as well:

#! /bin/sh

TF=$(mktemp)
trap "rm -f $TF" 0 1 2 3 15
checkstdin() {
sort -f | uniq -di > $TF
test -s $TF || return 0 # if $TF is empty, we are good
echo "non-unique (after case folding) names found!" 1>&2
cat $TF 1>&2
return 1
}

Now we just need to use it on files that will be committed, and perhaps on branch names as well. The former are listed with git ls-files, so:

git ls-files | checkstdin || {
echo "ERROR - file name collision, stopping commit" 1>&2
exit 1
}

You can fancy this up to use git diff-index --cached -r --name-only --diff-filter=A HEAD to check only added files, allowing existing case collisions to continue, and/or try to check things across many branches and/or commits, but that gets difficult.

Combine the above two fragments into one script (and test) and then simply copy it to an executable file named .git/hooks/pre-commit.

Checking branch names is a bit trickier. This really should happen when you create the branch name, rather than when you commit to it, and it's impossible to do a really good job on the client—it has to be done on a centralized server that has a proper global view.

Here is a way to do it on the server in a pre-receive script, in shell script rather than in Python (as in the linked answer). We still need the checkstdin function though, and you might want to do it in an update hook rather than a pre-receive hook, since you don't need to reject the entire push, just the one branch name.

NULLSHA=0000000000000000000000000000000000000000 # 40 0s

# Verify that the given branch name $1 is unique,
# even IF we fold all existing branch names' cases.
# To be used on any proposed branch creation (we won't
# look at existing branches).
check_new_branch_name() {
(echo "$1"; git for-each-ref --format='%(refname:short)' refs/heads) |
checkstdin || {
echo "ERROR: new branch name $1 is not unique after case-folding" 1>&2
exit 1 # or set overall failure status
}
}

while read oldsha newsha refname; do
... any other checks ...
case $oldsha,$refname in
$NULLSHA,refs/heads/*) check_new_branch_name ${refname#refs/heads/};;
esac
... continue with any other checks ...
done

How do I stop one more file with same name (having different case) getting created in git stage

I'm assuming you're on Windows and pushing to a Linux remote.

Try the steps below:

  1. Delete the file
  2. Commit the deletion
  3. Push the deletion commit
  4. Create the file again with the right name
  5. Commit the file
  6. Push the file

Why is this happening?

Because Windows does not make difference between upper or lower case letters but Linux does! So keep in mind to not name files equally no matter the letters case.

Update 1:

You may need to delete and clone the repository again after step 3 before continue to step 4.

git case-insensitive directory renamed

To remove the directory from git, but not filesystem

git rm -r --cached folderNameToRemove

Add the directory to .gitignore, and remember to do a git push after that.

For similar scenarios, checkout this brilliant thread

Two of the same directories on remote with different cases while only one locally

I only know of one way of fixing a casing issue like that, and that is by fixing it on a remote that supports case differences.

In other words, on the remote you have 2 folders, you can do one of the following, depending on how you want the end result to be:

  1. If you want the two folders to stay separate, rename one of them so that it has a different name, other than just a case difference.
  2. If you want the two folders to be just one, combine the contents into the one folder you want to keep, that is, the one with the right casing in its name. Note that depending on how you ended up in the situation you're in, the contents of the two folders might be different, they might be the same, or there may be a mix of files existing in both and files only existing in one of them.

After you've performed one or the other, commit, and share this new commit with your local clone.

Note: This will not fix the history. If you ever check out one of the flawed commits in your history, you might have problems again. If you want to fix that you need to rewrite history to get rid of the casing problem altogether.

Note: Also, make sure you fix the local folder to have the correct casing as well, or you might end up reintroducing the problem in your next commits. If you don't have uncommitted changes, it might be best to just wipe the local clone and reclone to make sure it is correctly in sync with the remote.

Add all files to a commit except a single file?

git add -u
git reset -- main/dontcheckmein.txt

Note: Git has subsequently added special syntax for this, which is explained in other answers.



Related Topics



Leave a reply



Submit