1. 概述

Git 已经成为广泛使用的分布式版本控制系统。在这个教程中,我们将探讨如何从 Git 仓库中移除文件或目录,但保留本地副本。

2. 问题介绍

通常,我们可以通过一个例子来理解这个问题。假设我们在处理一个名为 myRepo 的 Git 仓库:

$ 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

我们已将仓库克隆到本地,如 ls 输出所示,仓库中有三个文件和一个 logs 目录。

现在,假设我们想要从仓库中移除文件 user-list.txtlogs 目录,但不希望从本地工作副本中删除它们。常见的场景是,我们可能已经提交了一些文件或目录,然后意识到应该忽略某些文件。因此,我们将从仓库中移除相关文件,保留本地副本,并在 .gitignore 文件中添加相应的模式,以使 Git 不再追踪这些文件

我们知道使用 git rm user-list.txt 命令会从仓库中删除文件,但它也会删除本地文件。

当然,我们可以移动文件和目录到另一个目录,提交一次更改,然后再复制回本地工作目录。这可以解决问题,但这种方法效率不高,特别是当文件或目录很大时。

接下来,让我们看看如何更有效地解决这个问题。

3. 使用 git rm –cached 命令

我们提到过,git rm FILE 默认情况下会从索引和本地工作树中移除文件。

然而,git rm 命令提供了 –cached 选项,允许我们仅从仓库索引中移除文件,而不触及本地文件。

现在,让我们尝试对 user-list.txt 文件进行操作:

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

如上输出所示,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

如我们所见,user-list.txt 被标记为 "已删除"。由于其本地副本仍然存在,所以被标记为 "未跟踪"。

我们可以同样地移除 logs 目录,但由于它是一个目录,我们需要额外传递 -r(递归) 选项给 git rm 命令:

$ 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 命令检查当前暂存的文件:

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

输出显示,目标文件和目录已不再存在。同时,本地副本也被保留。因此,我们解决了问题。

如果我们愿意,我们可以将它们添加到 .gitignore 文件中,防止 Git 再次追踪它们。

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

有时,我们想要检查 Git 的索引并移除所有 .gitignore 中定义的文件。假设我们完成了对 .gitignore 的定义。那么,一个直接的方法是分三步进行:

  1. 从索引中移除所有文件:git rm -r –cached
  2. 再次暂存所有文件。.gitignore 中定义的文件将自动被忽略:git add
  3. 提交更改:git commit -m "合适的提交信息"

或者,我们可以找到并仅移除当前被跟踪但应被忽略的文件。git ls-files 命令可以帮助我们找到这些文件。

让我们撤销之前的提交,再次移除 user-list.txt 文件和 logs 目录。这次,我们先将它们添加到 .gitignore 文件中:

$ cat .gitignore
user-list.txt
logs/

接下来,找出我们想要从 Git 索引中移除的文件:

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

如我们所见,上述命令列出了我们想要移除的暂存文件。

现在,让我们结合 git rm –cachedgit ls-files 命令一次性完成它们的移除:

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

值得注意的是,命令将删除 logs 目录下的所有文件,最终会从索引中删除空的 logs 目录。在这个例子中,logs 目录下只有一个文件。

现在,如果检查暂存的文件,已删除的文件将不再存在:

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

当然,user-list.txtlogs/ 仍保留在我们的本地工作树中:

$ 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

5. 已移除的文件仍存在于 Git 历史中

我们使用 git rm –cached 命令解决了问题。但是,请记住,我们只是从 Git 的跟踪索引中移除了文件。文件及其内容仍然可以在 Git 的提交历史中看到。例如,我们仍可以通过查看先前的提交看到 user-list.txt 的内容:

$ 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

了解这一点很重要,因为有时我们可能忘记将某些敏感文件(如凭据)添加到 .gitingore 文件中,但已经提交了更改并推送到了远程仓库。当我们意识到这一点后,可能希望完全从 Git 历史中清除这些敏感文件。

如果是这种情况,我们需要从 Git 的提交历史中删除文件

6. 总结

在这篇文章中,我们通过示例展示了如何从 Git 仓库中移除文件或目录,同时保留本地副本。我们也讨论了一种快速移除 .gitignore 文件中定义的所有文件的方法。最后,我们应该记住,使用 git rm –cached 移除的文件仍然存在于 Git 的提交历史中。