Git: How to remove file from index without deleting files from any repository
When you use
git rm --cached myfile
it doesn't delete from the local filesystem, which is the goal开发者_运维百科. But if you've already versioned and committed the file, pushed it to a central repository, and pulled it into yet another repository before using the command, it will delete the file from that system.
Is there a way to just remove the file from versioning without deleting it from any filesystem?
Edit: Clarified, I hope.
I do not think a Git commit can record an intention like “stop tracking this file, but do not delete it”.
Enacting such an intention will require intervention outside Git in any repositories that merge (or rebase onto) a commit that deletes the file.
Save a Copy, Apply Deletion, Restore
Probably the easiest thing to do is to tell your downstream users to save a copy of the file, pull your deletion, then restore the file.
If they are pulling via rebase and are ‘carrying’ modifications to the file, they will get conflicts. To resolve such conflicts, use git rm foo.conf && git rebase --continue
(if the conflicting commit has changes besides those to the removed file) or git rebase --skip
(if the conflicting commit has only changed to the removed file).
Restore File as Untracked After Pulling a Commit That Deletes It
If they have already pulled your deletion commit, they can still recover the previous version of the file with git show:
git show @{1}:foo.conf >foo.conf
Or with git checkout (per comment by William Pursell; but remember to re-remove it from the index!):
git checkout @{1} -- foo.conf && git rm --cached foo.conf
If they have taken other actions since pulling your deletion (or they are pulling with rebase into a detached HEAD), they may need something other than @{1}
. They could use git log -g
to find the commit just before they pulled your deletion.
In a comment, you mention that the file you want to “untrack, but keep” is some kind of configuration file that is required for running the software (directly out of a repository).
Keep File as a ‘Default’ and Manually/Automatically Activate It
If it is not completely unacceptable to continue to maintain the configuration file's content in the repository, you might be able to rename the tracked file from (e.g.) foo.conf
to foo.conf.default
and then instruct your users to cp foo.conf.default foo.conf
after applying the rename commit.
Or, if the users already use some existing part of the repository (e.g. a script or some other program configured by content in the repository (e.g. Makefile
or similar)) to launch/deploy your software, you could incorporate a defaulting mechanism into the launch/deploy process:
test -f foo.conf || test -f foo.conf.default &&
cp foo.conf.default foo.conf
With such a defaulting mechanism in place, users should be able to pull a commit that renames foo.conf
to foo.conf.default
without having to do any extra work.
Also, you avoid having to manually copy a configuration file if you make additional installations/repositories in the future.
Rewriting History Requires Manual Intervention Anyway…
If it is unacceptable to maintain the content in the repository then you will likely want to completely eradicate it from history with something like git filter-branch --index-filter …
.
This amounts to rewriting history, which will require manual intervention for each branch/repository (see “Recovering From Upstream Rebase” section in the git rebase manpage).
The special treatment required for your configuration file would be just another step that one must perform while recovering from the rewrite:
- Save a copy of the configuration file.
- Recover from the rewrite.
- Restore the configuration file.
Ignore It to Prevent Recurrence
Whatever method you use, you will probably want to include the configuration filename in a .gitignore
file in the repository so that no one can inadvertently git add foo.conf
again (it is possible, but requires -f
/--force
).
If you have more than one configuration file, you might consider ‘moving’ them all into a single directory and ignoring the whole thing (by ‘moving’ I mean changing where the program expects to find its configuration files, and getting the users (or the launch/deploy mechanism) to copy/move the files to to their new location; you obviously would not want to git mv a file into a directory that you will be ignoring).
Had the very same issue this week when I accidentally committed, then tried to remove a build file from a shared repository, and this:
http://gitready.com/intermediate/2009/02/18/temporarily-ignoring-files.html
has worked fine for me and not mentioned so far.
git update-index --assume-unchanged <file>
To remove the file you're interested in from version control, then use all your other commands as normal.
git update-index --no-assume-unchanged <file>
If you ever wanted to put it back in.
Edit: please see comments from Chris Johnsen and KPM, this only works locally and the file remains under version control for other users if they don't also do it. The accepted answer gives more complete/correct methods for dealing with this. Also some notes from the link if using this method:
Obviously there’s quite a few caveats that come into play with this. If you git add the file directly, it will be added to the index. Merging a commit with this flag on will cause the merge to fail gracefully so you can handle it manually.
To remove the file from the index, use:
git reset mylife
This should not affect your local copy or anyone else's.
git rm --cached remove_file
- add file to gitignore
git add .gitignore
git commit -m "Excluding"
- Have fun ;)
After doing the git rm --cached
command, try adding myfile
to the .gitignore
file (create one if it does not exist). This should tell git to ignore myfile
.
The .gitignore
file is versioned, so you'll need to commit it and push it to the remote repository.
My solution is to pull on the other working copy and then do:
git log --pretty="format:" --name-only -n1 | xargs git checkout HEAD^1
which says get all the file paths in the latest comment, and check them out from the parent of HEAD. Job done.
The above solutions work fine for most cases. However, if you also need to remove all traces of that file (ie sensitive data such as passwords), you will also want to remove it from your entire commit history, as the file could still be retrieved from there.
Here is a solution that removes all traces of the file from your entire commit history, as though it never existed, yet keeps the file in place on your system.
https://help.github.com/articles/remove-sensitive-data/
You can actually skip to step 3 if you are in your local git repository, and don't need to perform a dry run. In my case, I only needed steps 3 and 6, as I had already created my .gitignore file, and was in the repository I wanted to work on.
To see your changes, you may need to go to the GitHub root of your repository and refresh the page. Then navigate through the links to get to an old commit that once had the file, to see that it has now been removed. For me, simply refreshing the old commit page did not show the change.
It looked intimidating at first, but really, was easy and worked like a charm ! :-)
精彩评论