2

I cloned a project onto my PC from a repository and got errors because a few files had asterisks in their filename which apparently can't exist on a Windows system. So now my git status shows that I am deleting those files since they didn't copy over to my project. I won't need to ever make any changes with these files (they're all images), so is there a way I can tell git not to push any changes (in this case specifically deletion) for these specific files?

I tried doing git update-index --assume-unchanged on one of them but it doesn't appear to have worked because my git status still shows the file as queued for deletion (picture relevant).

enter image description here

Brimby
  • 847
  • 8
  • 25
  • If my answer has helped you, don't hesitate to upvote. If it is the correct one, feel free to to accept it. – Cedric Nov 30 '18 at 10:56
  • 1
    Your answer is good and correct, but torek provided a way to apply skip-worktree to all the files at once, which was extra helpful. I did upvote you though @Cedric – Brimby Dec 01 '18 at 18:30

3 Answers3

2

Simply: git update-index --skip-worktree [<file>...]

With skip-worktree, even where git knows that the file has been modified (or needs to be modified by a reset --hard or the like), it will pretend it has not been, using the version from the index instead. This persists until the index is discarded. explanation.

See How do Iconfigure git to ignore some files locally : git update-index --skip-worktree

Cedric
  • 5,135
  • 11
  • 42
  • 61
1

TL;DR

Set the --skip-worktree bit for those particular paths in the index:

git ls-files -dz | xargs -0 git update-index --skip-worktree

(I assume your Windows-bash has xargs -0; if not, you might have to fiddle with this a bit.)

Long(ish)

Your git update-index command produced no errors, so it's not affecting these files, which means you're looking in the wrong place.

One fundamental issue here is that git push does not push files. Instead, it pushes commits. It's true that commits contain files, so you might argue that this is a useless distinction—but it's a key distinction that Git makes, and it's at the heart of both the problem, and the cure.

... a few files had asterisks in their filename which apparently can't exist on a Windows system

That appears to be correct (What characters are forbidden in Windows and Linux directory names?). In one sense, it doesn't affect or bother Git, though, because Git deals with commits, not files. It affects you. :-)

Git stores commits in its repository database. Git also stores each version of each file in this database. Each stored item, called an object internally, has a special, compressed, Git-only form. The files inside a commit are additionally frozen that way for as long as the commit exists (typically forever).1

Frozen (read-only), Git-only files are useless to you and the rest of the programs on your computer. So Git needs a way to extract them into read/write, useful form. It does this in two stages: first it effectively thaws the files by listing them in its index (which is really just a file named .git/index). The index keeps track of what is—or at least should be—in the work-tree. Then it de-compresses them and gives them useful names—real file names, in the real file system—and puts them in your work-tree.

It's the last step, of turning the thawed, but still Git-only, index entry into a file name like foo*bar, that fails because of the forbidden name. So the thawed entry is in the index, but is not in the work-tree.

Git always makes new commits from whatever is in the index. Even commands like git commit -a really just update the index, then build the commit from the index (though some use a temporary auxiliary index just for the duration of the commit, then fix up the real index). So until you change the copy of a file that's in the index, anything you do to the work-tree—including removing a file, or what Git thinks is removing since the file never got created—is just a change that is, in git status's terms, not staged for commit.

What we'd like, though, is to avoid having git status complain, and avoid having git add remove the index copy of the file. That is, if there was a file foo*bar, and it did not get into our work-tree, that's sort-of-OK, as long as we don't want to do anything with that file. We can leave the file in the index, missing from the work-tree, and have each new git commit we make re-freeze the index copy. It's just annoying that we have to tiptoe around the fact that there's no foo*bar in the work-tree.

Both of the index flags, --assume-unchanged and --skip-worktree, let us work around that fact. The --skip-worktree is the flag that's meant for this purpose: the Git guys added it for "sparse checkout", and this is a special case of a sparse checkout. So we can just find the list of files that git status will consider deleted from the work-tree—i.e., files that are listed in the index, are missing from the actual work-tree, and don't have the special flags set in the index—and set the --skip-worktree flag.

git ls-files -d prints a list of such file names. With -z, it prints them terminated by an ASCII NUL (\0) character instead of a newline. The xargs -0 command reads these NUL-terminated strings and runs git update-index --skip-worktree on them, so now all those index entries have the flag set. New commits will contain the files (since the index lists them), but the work-tree won't (since it can't); meanwhile git add won't try to remove them, and git status won't complain about them.


1This is actually part of a general rule: Git's internal objects live in a key-value store addressed by keys that are hash IDs, and the hash ID of any object is a unique, cryptographic checksum of the object's content. The content cannot be changed because if it were changed, the checksum would change, after which the old key would still retrieve the old object, with only the new key / hash-ID able to retrieve the new object.

torek
  • 448,244
  • 59
  • 642
  • 775
  • So `git ls-files -d` prints a list of all files that are committed to deletion? I only have 5 files so even if the flags aren't set up right I could run skip-worktree on each filename manually. Very cool solution though. – Brimby Nov 29 '18 at 23:01
  • Nothing is committed (for any purpose at all, deletion, change, new file, whatever) yet! Whenever you run `git commit`, whatever is in the *index* goes into the new commit. If the file is in the index but not in the work-tree, the index copy of the file goes into the new commit. We're not going to change this: the copy that's in the index, named with the asterisk, is OK, and you still want it in your own new commits. You can't *change* it, but that is also OK. You just want to make Git stop *complaining* about it, and for that purpose, the `--skip-worktree` bit is perfect. – torek Nov 29 '18 at 23:51
0

As it was said a few days ago about similar question, it's could be a shell issue and not a git issue.

Have you tried git bash instead of windows shell ?

Have you tried to rename these file, just after pull, using escape character for the asterisk ?

phili_b
  • 885
  • 9
  • 27
  • I used git bash, actually. I use it exclusively instead of powershell. I'm not sure how I could rename the file after pull because the files never actually get cloned onto my system. They are missing after the pull. – Brimby Nov 29 '18 at 22:54
  • Can you try to copy by hand and re-type `git status` after that ? – phili_b Nov 29 '18 at 23:15