Search code examples
gitversion-controlgitignore

Locally delete and ignore folder without commiting


There is a folder in our repository that others depend on, but I would like to delete for my local working copy. I would like to make sure that I don't accidentally commit this change. If I just delete the folder, my git index is spammed with hundereds of deletions from that folder. Is there a way to make git ignore the deletion of that folder?

I tried adding the folder path to .git/info/exclude but that seems to be indendet to prevent local files from being commited.


Solution

  • The short answer is no. There's a longer answer that is yes but—specifically, yes, but be careful with this! Specifically, you can set the skip-worktree bit in the index. Git's sparse checkout mode uses this bit, and you can use it yourself for the same effect:

    git update-index --skip-worktree <list of files>
    

    though in this case you might want to just set up sparse checkouts in the first place.

    Long

    The index is indexing (hence the name index) your work-tree: it lists all the files that should be present, and has their content that will in fact be in the next commit you make, unless of course you modify the index before making that next commit. I'll get to what should be present really means in a bit, but let's look at the normal case first.

    If you remove all the files (and their containing directory / folder) from your work-tree, Git notices that your index and your work-tree do not match, and tells you that you have these differences, i.e., that all these files are deleted. They are not staged for commit because the files are still in the index, and the copy of each file in the index matches the copy of each file in the current commit. But they are missing from the work-tree: they are deleted.

    If you remove the files from the index as well (using git rm --cached), your index and work-tree will now match up: the index won't list dir/file, and dir/file won't actually exist. So this part of git status output will no longer complain about dir/file. But if you do that, now your current commit, which Git calls HEAD or @, does have dir/file, and your index doesn't have dir/file, so the next commit you would make would also not have dir/file. That means that a delete of dir/file is staged for commit and git status will report that.

    In other words, if dir/file is missing from the work-tree, but is in @ (the current or HEAD commit), you have only two options:

    • dir/file is in the index. Then it's deleted in terms of index-vs-work-tree. git status will say so.

    • dir/file is not in the index. Then it's deleted in terms of @-vs-index. git status will say so.

    If you do not intend to make a commit that lacks dir/file, the former is clearly the better choice—but it's still noisy.

    What should be present means

    1Note that if the index says some file with path P should exist, and it does exist, all is well. The file data in P in the work-tree matches, or doesn't match, the data in P in the index, and the file data in P in the index matches, or doesn't match, what's in the @ or HEAD commit.

    On the other hand, if the index says that the file with path P does not exist in the work-tree, but in fact that file does exist in the work-tree, then path P represents an untracked file. This is where .gitignore and other exclude files come in: you can tell git status don't complain about P by listing it in a .gitignore.

    The third case is of course the one you're looking at: the file with path P does exist in the index, but is missing from the work-tree.

    Sparse checkouts and the skip-worktree bit

    Git has built into it direct support for what Git calls a sparse checkout. To do this, you set the configuration variable core.sparseCheckout to true, and list paths (or pathspecs) in .git/info/sparse-checkout. For details, see, e.g., Is it possible to do a sparse checkout without checking out the whole repository first? The git read-tree documentation section on sparse checkouts has more about this as well.

    The way sparse checkouts work is that Git reads the entire commit into the index as usual, but instead of copying all the corresponding files into the work-tree, it only copies the selected files into the work-tree. For the remaining files, which may or may not be in the work-tree—because Git won't look—it sets the skip-worktree bit in the index entry for that file.

    When git status or other Git operations are scanning through the actual work-tree to see what's really there, if they come across an index entry with the skip-worktree bit set, they pretend that the file is there, with the contents listed in the index. They assiduously avoid checking to see if the file really is there or not. What the index claims, according to the skip-worktree bit, must be assumed to be true.

    If you use sparse checkout, Git will maintain the skip-worktree bits by itself (to at least some extent—I have not experimented with the feature to see where it might have rough edges). Otherwise you have to maintain them yourself, manually setting (git update-index --skip-worktree) and clearing (git update-index --no-skip-worktree) the bit for each file.