让Git遗忘已跟踪但现被列入.gitignore的文件的方法
技术背景
.gitignore
文件用于防止未跟踪的文件被添加到Git的跟踪文件集中,但对于已经被Git跟踪的文件,.gitignore
不会使其停止跟踪。因此,当我们将原本已被跟踪的文件添加到 .gitignore
后,需要额外的操作让Git遗忘这些文件。
实现步骤
方法一:使用 git update-index --skip-worktree
如果你想保留本地文件,但从Git跟踪中移除它,可以使用以下命令:
1
| git update-index --skip-worktree <file>
|
若要取消该操作,可使用:
1
| git update-index --no-skip-worktree <file>
|
方法二:使用 git rm --cached
此方法会从索引中移除文件,下次提交时该文件将从HEAD版本中移除。
1
| git rm -r --cached <folder>
|
也可以通过以下一系列命令移除Git索引中的所有项,并更新索引,同时遵循 .gitignore
规则:
1 2 3
| git rm -r --cached . git add . git commit -am "Remove ignored files"
|
或者使用单行命令:
1
| git rm -r --cached . && git add . && git commit -am "Remove ignored files"
|
方法三:使用 git filter-branch
此方法会从整个历史记录中删除文件,使用前请备份文件。
1
| git filter-branch --index-filter 'git rm --cached --ignore-unmatch filename' HEAD
|
通用步骤
当需要让Git遗忘已跟踪但现被列入 .gitignore
的文件时,可按以下通用步骤操作:
- 提交所有更改:确保所有更改(包括
.gitignore
文件的更改)都已提交。 - 移除仓库中的所有跟踪文件:
- 重新添加所有文件:
- 提交更改:
1
| git commit -m ".gitignore fix"
|
最后,将更改推送到远程仓库:
核心代码
移除单个文件示例
1 2 3 4 5
| git rm --cached test.txt git add . git commit -m "test.txt removed" git push
|
移除文件夹示例
1 2 3 4 5
| git rm -r --cached .idea git add . git commit -m ".idea removed" git push
|
最佳实践
- 对于配置文件等每个开发者有自己独立配置的文件,使用
git update-index --skip-worktree
可以避免远程内容覆盖本地配置。 - 当需要从整个历史记录中删除敏感文件时,使用
git filter-branch
命令,但要注意备份文件。 - 在执行可能影响历史记录的操作(如
git filter-branch
)前,确保所有开发者都了解并做好备份。
常见问题
使用 git rm --cached
后他人拉取文件被删除
使用 git rm --cached
会导致其他开发者在执行 git pull
时,文件在他们的文件系统中被删除。可以采用以下替代方案:
- 让应用程序查找被忽略的文件
config-overide.ini
并使用它覆盖已提交的文件 config.ini
。 - 提交
config-sample.ini
文件并忽略 config.ini
,必要时使用脚本复制文件。 - 尝试使用
gitattributes
的 clean/smudge
功能处理配置文件的更改。 - 将配置文件放在专用的部署分支上,不合并到主分支。
文件名包含特殊字符(如换行符)时处理失败
使用以下命令可以安全处理包含特殊字符的文件名:
1 2
| git ls-files -z --ignored --exclude-standard | xargs -0 git rm -r --cached git commit -am "Remove ignored files"
|
使用 git filter-branch
后远程仓库处理问题
使用 git filter-branch
更改历史记录后,需要临时允许非快进式推送,并通知其他开发者使用 rebase
而非 merge
处理分支。具体命令如下:
1 2 3 4 5 6 7
| git fetch --all git filter-branch --force --index-filter 'git rm --cached --ignore-unmatch full/path/to/file' --prune-empty --tag-name-filter cat -- --all git push origin --force --all git push origin --force --tags git for-each-ref --format='delete %(refname)' refs/original | git update-ref --stdin git reflog expire --expire=now --all git gc --prune=now
|
其他开发者拉取修改后的远程仓库时,可按以下步骤操作:
1 2
| git fetch --all git reset FETCH_HEAD
|