1

Is it possible to remove a file from git, while keeping the local files for everyone, even after a pull?

It's a file that was missing from .gitignore. Is there a way to correct the mistake?

I've seen similar questions, but as far as I know:

  • --assume-unchanged is for the local git repo only
  • --skip-worktree keeps the file in git
  • git rm removes the files for other users after a pull

Edit:
I'm looking for a solution without manually backing up files or rewriting history.
Also I can accept it if it's not possible.

user
  • 6,567
  • 18
  • 58
  • 85
  • 1
    Does this answer your question? [Remove a file from a Git repository without deleting it from the local filesystem](https://stackoverflow.com/questions/1143796/remove-a-file-from-a-git-repository-without-deleting-it-from-the-local-filesyste) – axiac Feb 01 '20 at 20:51
  • 1
    @axiac I believe this will remove the file if others perform a `git pull` – user Feb 01 '20 at 20:57
  • 3
    ... which is something you **can't** avoid happening. You will have to ask your buddies to save it (copying it) before pulling so that it can be reinstated. – eftshift0 Feb 01 '20 at 21:09
  • 1
    Did you try this then: https://stackoverflow.com/a/34745670/1997776? There's a comment that says you can avoid the files from disappearing when someone does `git pull`. Is this helpful to you? – Aulis Ronkainen Feb 01 '20 at 21:17
  • @AulisRonkainen This would need a force push. However if these are really the only options (copying or force push), I can live with it. – user Feb 01 '20 at 21:27
  • 1
    Yes, you would need to force push. It does rewrite the history of the whole branch, to be fair. Why is the force push an issue for you? Just curious. – Aulis Ronkainen Feb 01 '20 at 21:33
  • @AulisRonkainen Not sure what are the implications if there are open (not merged) feature branches when the force push happens. Could you still merge those back to the rewritten branch without too much extra work? – user Feb 01 '20 at 22:12
  • Does this answer your question? [Remove and untrack files from repo but keep them on remote server](https://stackoverflow.com/questions/37146402/remove-and-untrack-files-from-repo-but-keep-them-on-remote-server) – phd Feb 01 '20 at 22:58
  • 1
    There is no way to prevent deleting of the file. Even locally: if you switch to a different branch and switch back the file will be deleted from your filesystem. – phd Feb 01 '20 at 22:59
  • 1
    People keep saying there is "no way" to avoid deletion, and as noted below that is not true. People need to recognize the difference between "I don't know a way" and "there is no way". However, the point about branch-switching is worth an extra note (which I will add to the answer). – Mark Adelsberger Feb 02 '20 at 19:58

1 Answers1

1

As I understand your constraints, you want the users to keep their files without doing anything special. It isn't really possible. If you can tell them to perform a simple procedure, there's a work-around - but after due consideration, I don't recommend it at all.

First, regarding the methods you mention in the question:

--assume-unchanged and --skip-worktree are the wrong answer almost every time they are suggested. They each have a specific purpose for which they were created and shouldn't really be used for other things.

git rm removes a file from the index (and optionally also your local working tree). In a normal workflow, that means the next commit won't contain the file even if the previous one did. But if, by that or any other means, a file is present in the index and you then check out a commit that doesn't contain that file, the file is removed from the worktree. This is true regardless of the .gitignore status of the file (both in the old and new commit).

Now, previously when I've described this, I've said (somewhat erroneously) "...a file is present in the currently-checked-out commit and you then check out a commit that doesn't contain the file...". In most cases that was close enough to correct as to not matter, but the difference does give us the potential workaround:

Under the correct conditions, each user can remove the file from the index before pulling the new commit, and this should avoid local deletion of the file.

git rm --cached <path/to/file>
git pull

One caveat (and how to deal with it):

The file must be subject to exclusion rules, or git will see the unstaged copy of the file as reason not to allow the checkout. If the user has already pulled a commit with a fixed .gitignore (which also assumes you fixed .gitignore in a commit before the one where you remove the file) then this will pose no problem. Otherwise they can temporarily add the file to the .git/info/exclude file in their local repo.

The problem, if you have more than just the master branch, is that now you have to worry about the file being deleted every time you move between branches (becaues changing to any given branch might re-add the file to the index, and then moving back to master would delete the file).

Even without branch-switching, every time you restore history from before the file was removed, you risk losing the local copy when returning to a current version. (At this point someone always likes to say how rare it is to check out historical versions... which I find silly, because if I'm not planning for being able to go back in time, what is source control for, really?)

So using this workaround instead of doing a rewrite trades a one-time problem for an ongoing one. Which I can't say I recommend. If you must, then I guess you can mitigate the ongoing issue by creating a local hook that unstages the offending file after every checkout - so that it will always be kept out of the index and subsequent checkouts can't cause the file to be deleted; but

1) I can't guarantee that this won't have other unforeseen side-effects

2) You'll have to communicate to each user that they need to add the hook, and add the file to their local .git/info/exclude file (because otherwise git will reject checkouts that would've otherwise deleted the file)... and at that point, where you're having to coordinate such effort by every repo user, there's no reason not to just do the rewrite and be done with it.

In fact it's worse than that, because every new clone will need to have the hook set up and the file added to the exclude file as well, whereas if you do the rewrite once, then you are done with it.

Mark Adelsberger
  • 42,148
  • 4
  • 35
  • 52
  • I see the limitations of this approach. Could you please also write about the caveats of the other solution which involves rewriting branch history? (For example when there are multiple long lived branches / feature branches open.) – user Feb 03 '20 at 09:38
  • 1
    @user - That isn't really in the scope of the answer, and it's already a rather long answer. Of course rewriting shared history has a cost of coordination, and you can find a number of posts (and the git rebase docs) that discuss this. tl;dr - For such a sweeping rewrite the best process is: Tell all the devs to push (not necessarily merge) their changes because a rewrite is coming. Set a date/time when they must discard their locals. Then do the rewrite and everyone re-clones. Then it's done. – Mark Adelsberger Feb 03 '20 at 16:44