1. 概述

Git 是目前最流行的分布式版本控制系统。本文将介绍如何从 Git 仓库中移除文件或目录,同时保留其在本地的副本。这对于处理误提交、调整 .gitignore 规则等场景非常实用。

2. 问题背景

我们通过一个实际例子来说明这个问题。假设你有一个本地克隆的 Git 仓库 myRepo,目录结构如下:

$ ls -l
total 12
drwxr-xr-x 2 kent kent 60 May 12 23:00 logs/
-rw-r--r-- 1 kent kent 26 May 11 13:22 README.md
-rw-r--r-- 1 kent kent 21 May 11 13:22 some-file.txt
-rw-r--r-- 1 kent kent 16 May 12 22:40 user-list.txt

现在你希望从 Git 仓库中删除 user-list.txt 文件和 logs 目录,但不希望删除本地的文件副本

一个典型的场景是:你提交了一些文件,后来发现应该忽略它们。此时,你希望:

  • 从 Git 中移除这些文件
  • 保留本地副本
  • 将这些文件添加到 .gitignore 中,避免再次被提交

我们知道 git rm user-list.txt 会从仓库和本地都删除文件,这不符合我们的需求。

3. 使用 git rm --cached 命令

默认情况下,git rm FILE 会同时从索引和本地工作目录中删除文件。但 Git 提供了一个 --cached 选项,可以只从索引中删除,而保留本地文件。

我们来试试删除 user-list.txt

$ git rm --cached user-list.txt
rm 'user-list.txt'

接着用 git status 查看状态:

$ git status
On branch master
Your branch is up to date with 'origin/master'.

Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
    deleted:    user-list.txt

Untracked files:
  (use "git add <file>..." to include in what will be committed)
    user-list.txt

✅ 说明:文件已从 Git 中删除,但本地仍然存在,被标记为 "untracked"。

同理,删除 logs 目录时需加上 -r 选项(递归删除):

$ git rm --cached -r logs
rm 'logs/server.log'

提交更改:

$ git commit -m 'remove user-list.txt and logs'
[master ee8cfe8] remove user-list.txt and logs
 2 files changed, 4 deletions(-)
 delete mode 100644 logs/server.log
 delete mode 100644 user-list.txt

验证当前索引内容:

$ git ls-files -c
.gitignore
README.md
some-file.txt

⚠️ 说明:被删除的文件已不在 Git 索引中,但本地依然存在。

如果你希望防止这些文件再次被提交,记得将它们加入 .gitignore

$ cat .gitignore
user-list.txt
logs/

4. 一次性移除所有 .gitignore 中定义的文件

有时候你已经完善了 .gitignore 文件,希望从 Git 中一次性移除所有被忽略的文件。可以使用以下步骤:

✅ 方法一:清空索引后重新添加

git rm -r --cached .
git add .
git commit -m "Remove all files defined in .gitignore"

⚠️ 说明:这会重置整个索引,适用于你已经调整好 .gitignore 的情况。

✅ 方法二:仅移除当前被忽略的文件

先将目标文件加入 .gitignore

$ cat .gitignore
user-list.txt
logs/

然后使用 git ls-files 找出当前被追踪但应被忽略的文件:

$ git ls-files -i -c -X .gitignore
logs/server.log
user-list.txt

接着使用 git rm --cached 删除这些文件:

$ git rm --cached $(git ls-files -i -c -X .gitignore)
rm 'logs/server.log'
rm 'user-list.txt'

最后确认索引内容:

$ git ls-files -c
.gitignore
README.md
some-file.txt

再检查本地文件是否仍然存在:

$ ls -l
total 12
drwxr-xr-x 2 kent kent 60 May 13 00:45 logs/
-rw-r--r-- 1 kent kent 26 May 11 13:22 README.md
-rw-r--r-- 1 kent kent 21 May 11 13:22 some-file.txt
-rw-r--r-- 1 kent kent 16 May 13 00:45 user-list.txt

✅ 说明:本地文件完好,Git 索引已更新。

5. 被删除的文件仍存在于 Git 历史中

虽然我们已经从 Git 中删除了文件,但这些文件仍然保留在 Git 的提交历史中。例如:

$ git show 668fa2f user-list.txt
commit 668fa2f...
Author: ...
Date:   ...

    add user-list.txt and some-file.txt

diff --git a/user-list.txt b/user-list.txt
new file mode 100644
index 0000000..3da7fab
--- /dev/null
+++ b/user-list.txt
@@ -0,0 +1,3 @@
+kent
+eric
+kevin

⚠️ 踩坑提醒:如果你曾不小心提交过敏感文件(如密码、私钥等),并推送到远程仓库,即使使用 git rm --cached 也无法彻底删除历史记录。这种情况下,你需要使用专门工具如 BFG Repo-Cleanergit filter-branch 来清理 Git 历史。

6. 总结

本文介绍了以下内容:

  • 如何使用 git rm --cached 从 Git 中删除文件或目录,同时保留本地副本
  • 如何一次性清理所有 .gitignore 中定义的文件
  • 被删除文件仍然存在于 Git 历史记录中,需注意敏感数据清理

✅ 使用建议:

场景 推荐命令
删除单个文件 git rm --cached file.txt
删除目录 git rm --cached -r dir/
清理所有被忽略文件 git rm -r --cached . && git add .
敏感文件彻底清理 使用 BFG Repo-Cleanergit filter-branch

掌握这些技巧可以让你更高效地管理 Git 仓库,避免误提交和不必要的文件追踪。


原始标题:Remove File From Git Repository Without Deleting It Locally