Git More --1
这是阅读Pro Git内容的一些笔记。以下内容包括该书开头到2.2节的内容。
版本控制系统的一些过去
在Git出现之前,版本控制系统(Version Control System, VCS)主要有两种类型:集中式版本控制系统(Centralized VCS)和分布式版本控制系统(Distributed VCS)。但在这两种类型之前,还有一种更早期的版本控制方法,称为本地版本控制系统(Local VCS)。
本地版本控制系统
在本地版本控制系统中,版本控制是通过在本地文件系统上创建文件的备份来实现的。开发者会手动复制文件,并将其保存在不同的目录中,以表示不同的版本。这种方法虽然简单,但存在一些明显的缺点,比如难以管理多个版本、容易出错以及无法协作等。
由此,本地版本控制系统逐渐被集中式版本控制系统所取代。
集中式版本控制系统
集中式版本控制系统(如CVS、Subversion)引入了一个中央服务器,所有的代码和版本历史记录都存储在这个服务器上。开发者通过网络连接到服务器,进行代码的提交、更新和查看历史记录等操作。这种方法解决了本地版本控制系统的一些问题,比如协作和版本管理,但也带来了一些新的挑战,比如对网络连接的依赖以及单点故障的问题。如果中央服务器出现故障,所有开发者都无法访问代码库。这对大型工程来讲是一个重大的风险。
于是,分布式版本控制系统应运而生。
分布式版本控制系统
与集中式版本控制系统不同,分布式版本控制系统(如Git、Mercurial)允许每个开发者在本地拥有完整的代码库和历史记录的副本。这样,即使中央服务器出现问题,开发者们仍然可以继续工作,并在服务器恢复后同步他们的更改。这种方法不仅提高了系统的可靠性,还增强了协作的灵活性。
Git 的特性
- 直接记录快照: 与其他基于差异的版本控制系统(比如CVS、Subversion)不同的是,Git不保存一个基本文件和一系列补丁,而是保存一系列对所有文件系统的历史快照和快照的索引。如果某个文件没有修改,那么Git不会再保存这个文件,而是保存一个指向之前相同文件的链接。
- 本地执行: 同时,Git是分布式的,每个开发者的电脑上都有完整的代码库和历史记录,这样即使没有网络连接,也可以进行提交、查看历史等操作,也因此Git拥有非常快的速度。
- 数据完整性: Git使用SHA-1哈希值来唯一标识每个对象(文件、目录、提交等),这样可以确保数据的完整性,防止数据被篡改。
- 一般只追加操作: Git中的大多数操作都是追加操作,这样可以最大限度地减少数据丢失的风险。
三种状态
在Git中,文件可以处于三种状态之一:已修改(modified)、已暂存(staged)和已提交(committed)。理解这三种状态对于有效地使用Git非常重要。
- 已修改(modified): 当你对文件进行更改后,文件处于已修改状态。这意味着文件的内容已经发生变化,但这些更改还没有被记录到Git的版本历史中。
- 已暂存(staged): 这表示对已修改的文件做好了标记,并已经准备好被提交到下一个快照中。
- 已提交(committed): 这代表这些数据已经被安全地存储在本地数据库中,形成了一个新的版本快照。
由此,一个典型的Git项目将包括三个区域:工作目录(working directory)、暂存区(staging area)和Git目录(Git directory)。工作目录是你实际进行文件编辑的地方,暂存区是一个中间区域,用于准备即将提交的更改,而Git目录则是Git用来存储所有版本历史记录和对象的地方。

基本的工作流程通常如下:
- 在工作目录中修改文件,文件变为已修改状态。
- 将想要提交的更改添加到暂存区,文件变为已暂存状态。
- 提交暂存区的更改到Git目录,文件变为已提交状态。
记录和更新状态
当你在Git目录中工作时,文件都只有两种状态:已跟踪(tracked)和未跟踪(untracked)。已跟踪的文件指的是那些已经被纳入版本控制的文件,也就是说上次快照中有它们的身影;未跟踪的文件则是那些新创建的文件,Git还没有将它们纳入版本控制。
当你编辑过一个已跟踪的文件后,它会变为已修改状态。如果你想将这些更改纳入下一个提交,你需要先将它们添加到暂存区,这样它们就变为已暂存状态。相反,未跟踪的文件不会自动被纳入版本控制,除非你明确地将它们添加到Git中。

查看和更改文件的状态
使用git status命令可以查看当前工作目录和暂存区的状态,了解哪些文件是已修改、已暂存或未跟踪的。
当你刚克隆一个Git仓库时,通常会看到如下输出:
1 | On branch main |
这表示你当前在main分支上,并且工作目录是干净的,没有任何未提交的更改。
如果你创建了一个新文件example.txt,然后运行git status,你会看到类似如下的输出:
1 | Untracked files: |
这表示example.txt是一个未跟踪的文件,Git还没有将它纳入版本控制。如果你想将它添加到版本控制中,可以使用git add example.txt命令,然后再次运行git status,你会看到:
1 | Changes to be committed: |
这表示example.txt现在已经被添加到暂存区,准备提交。
现在,假设你对example.txt进行了修改,然后再次运行git status,你会看到:
1 | Changes to be committed: |
这表示example.txt已经被添加到暂存区,但你对它进行了进一步的修改,这些修改还没有被添加到暂存区。如果你想将这些修改也纳入下一个提交,可以再次使用git add example.txt命令。
然后我们再来看另外一种情况,假设你有一个已跟踪的文件example.txt,你对它进行了修改,然后运行git status,你会看到:
1 | Changes not staged for commit: |
这表示example.txt是一个已跟踪的文件,并且它已经被修改,但这些修改还没有被添加到暂存区。如果你想将这些修改纳入下一个提交,可以使用git add example.txt命令。
看起来相当繁琐是吧?不过熟悉之后就会觉得很自然了。当然,后面我们也会谈到如何跳过暂存区,直接提交修改。
状态简览
git status命令输出的内容有时候过于详细了,当你开始熟悉Git之后,可以使用git status -s命令来获得简洁的状态输出。例如:
1 | M example.txt |
在这个简洁的输出中,每一行表示一个文件的状态。第一列表示文件在暂存区的状态,第二列表示文件在工作目录的状态。具体含义如下:
(空格):表示文件没有变化。M:表示文件被修改了(Modified)。A:表示文件被添加到暂存区(Added)。D:表示文件被删除了(Deleted)。R:表示文件被重命名了(Renamed)。C:表示文件被复制了(Copied)。??:表示文件是未跟踪的(Untracked)。!!:表示文件被忽略了(Ignored)。U:表示文件存在冲突(Unmerged)。
在上面的例子中,example.txt在工作目录中被修改了但还没有添加到暂存区,Rakefile曾被修改后添加到暂存区,然而之后又被修改且尚未将新的修改添加到暂存区,lib/git.rb被添加到了暂存区,README.md在工作目录中被修改了但还没有添加到暂存区,而new_file.txt是一个未跟踪的文件。
忽略文件
有时候,我们不希望Git跟踪某些文件,比如编译生成的二进制文件、日志文件或临时文件等。为此,Git提供了一个名为.gitignore的文件,用于指定哪些文件或目录应该被忽略,不纳入版本控制。
你可以在项目的根目录下创建一个名为.gitignore的文件,并在其中列出要忽略的文件或目录的模式。例如:
1 | cat .gitignore |
在上面的例子中,.gitignore文件指定了三种模式:忽略所有以.log结尾的文件,忽略所有以.o或.a结尾的文件,以及忽略所有以~结尾的备份文件。在初始化Git仓库时,最好要养成设置.gitignore文件的习惯,这样可以避免不必要的文件被纳入版本控制,保持代码库的整洁。
通常来讲,.gitignore文件的格式如下:
- 空行或以
#开头的行被视为注释,它们都会被忽略。 - 可以使用glob模式匹配,它会递归地应用于整个工作区中
- 匹配模式可以以斜杠(
/)开头以防止递归匹配。 - 以斜杠(
/)结尾的模式表示指定目录。 - 以感叹号(
!)开头的模式表示取消忽略某些文件或目录。
所谓的 glob 模式是指 shell 所使用的简化了的正则表达式。 星号(*)匹配零个或多个任意字符;[abc] 匹配任何一个列在方括号中的字符 (这个例子要么匹配一个 a,要么匹配一个 b,要么匹配一个 c); 问号(?)只匹配一个任意字符;如果在方括号中使用短划线分隔两个字符, 表示所有在这两个字符范围内的都可以匹配(比如 [0-9] 表示匹配所有 0 到 9 的数字)。 使用两个星号(**)表示匹配任意中间目录,比如 a/**/z 可以匹配 a/z 、 a/b/z 或 a/b/c/z 等。
查看已暂存和未暂存的更改
如果git status的输出对你来说太过于笼统了,你可以使用git diff命令来查看具体的更改内容。默认情况下,git diff显示的是工作目录和暂存区之间的差异,也就是未暂存的更改。例如:
1 | $ git diff |
在上面的输出中,git diff显示了CONTRIBUTING.md文件的具体更改内容。以-开头的行表示被删除的内容,以+开头的行表示新增的内容。
如果你想查看已经暂存但还没有提交的更改,可以使用git diff --staged命令(或者git diff --cached,两者是等价的)。例如:
1 | $ git diff --staged |
但是这里就无法显示未暂存的更改了。
提交更新
现在,暂存区已经准备就绪,可以提交了。但在此之前,你最好先使用git status命令确认一下当前的状态,确保所有想要提交的更改都已经被添加到暂存区。
一旦确认无误,可以使用git commit命令来提交更改。例如:
1 | # Please enter the commit message for your changes. Lines starting |
在上面的例子中,Git打开了一个文本编辑器,让你输入提交信息。你可以在编辑器中输入一条简短而有意义的提交信息,描述这次提交所包含的更改内容。保存并关闭编辑器后,Git会完成提交操作,并将更改记录到版本历史中。
你也可以使用-m选项直接在命令行中提供提交信息,例如:
1 | git commit -m "Add README and update CONTRIBUTING.md" |
这条命令会将暂存区的更改提交到版本历史中,并附加一条简短的提交信息。
跳过使用暂存区
有时候,你可能想要跳过暂存区,直接将工作目录中的更改提交到版本历史中。为此,你可以使用-a选项与git commit命令结合使用。例如:
1 | git commit -a -m "Update CONTRIBUTING.md" |
这条命令会自动将所有已跟踪的文件的更改添加到暂存区,并立即提交这些更改到版本历史中。需要注意的是,使用-a选项只会影响已跟踪的文件,未跟踪的文件仍然需要使用git add命令手动添加到暂存区。
移除文件
如果你想要从Git仓库中移除一个文件,你需要先将它从工作目录中删除,然后使用git rm命令将其从Git的版本控制中移除。例如:
1 | rm example.txt |
假如你只是手动地将文件删除,而不使用git rm命令将它从版本控制中移除,那么当前文件的状态将会显示为已删除(deleted),但它仍然存在于Git的版本历史中(即git staus将显示changes not staged for commitment)。
比如,你删除了一个尚未提交的已跟踪的文件example.txt,然后运行git status,你会看到:
1 | touch example.txt |
你需要使用git rm命令来告诉Git这个文件已经被删除并在暂存区中删除这个文件的记录,然后再进行提交。比如:
1 | git rm example.txt |
如果该文件之前是未跟踪的文件,直接删除即可,无需使用git rm命令。但是如果要删除之前修改过或者已经放到暂存区的文件,必须使用强制删除选项,即使用git rm -f <file>命令。使用这一命令的后果是,该文件会从工作目录和Git的版本控制中同时被删除。因此必须谨慎使用这个命令,此命令删除的文件将无法由Git恢复。
但是,如果你只是想将文件从版本控制中移除(也就是从暂存区中移除),但仍然希望保留该文件在工作目录中,可以使用--cached选项。例如:
1 | git rm --cached example.txt |
这条命令会将example.txt从Git的版本控制中移除,但文件仍然保留在工作目录中。我们也可使用glob模式来批量移除文件,比如:
1 | git rm --cached *.log |
这些命令会将所有以.log结尾的文件从Git的版本控制中移除(也就是Git仓库中移除),但这些文件仍然保留在工作目录中。
移动文件
如果你想要移动或重命名一个文件,最好使用git mv命令,而不是直接使用操作系统的移动命令。git mv命令会同时更新Git的版本控制信息,确保文件的历史记录得以保留。例如:
1 | git mv old_filename.txt new_filename.txt |
实际上,git mv命令相当于执行了以下三个步骤:
1 | mv old_filename.txt new_filename.txt |
无论采取哪种方式,Git都会识别出文件的重命名操作,并在提交历史中保留文件的变更记录。
小结
- Git是一种分布式版本控制系统,允许每个开发者在本地拥有完整的代码库和历史记录的副本。
- Git中的文件可以处于已修改、已暂存和已提交三种状态。
- 使用
git status命令可以查看当前工作目录和暂存区的状态。 - 使用
.gitignore文件可以指定哪些文件或目录应该被忽略,不纳入版本控制。 - 使用
git diff命令可以查看具体的更改内容。 - 使用
git add命令可以将文件添加到暂存区。 - 使用
git commit命令可以将暂存区的更改提交到版本历史中。 - 使用
git commit -a命令可以跳过暂存区,直接提交已跟踪文件的更改。 - 使用
git commit -m "message"命令可以在命令行中直接提供提交信息。 - 使用
git rm命令可以从Git的版本控制中移除文件。 - 使用
git rm --cached命令可以将文件从版本控制中移除,但保留在工作目录中。 - 使用
git mv命令可以移动或重命名文件,同时更新Git的版本控制信息。
2026年1月17日。