一、 引言:窥探Git的"心脏"------.git目录
在上一篇中,我们成功创建了Git仓库,并了解到其核心是一个名为 .git的隐藏目录。这个目录是Git真正的"大脑"和"心脏",它保存了项目所有的版本控制信息。理解 .git目录的结构和工作原理,能让我们对版本控制有更本质的认识,在遇到问题时也能知其所以然。
同时,在实际开发中,我们不仅需要添加新文件,更重要的是对已有文件进行频繁的修改。如何让Git有效地追踪这些变化,是每个开发者必须掌握的基本功。本篇我们将首先深入 .git目录,然后重点学习如何使用Git来管理文件的修改、查看状态和比较差异。
二、 深入剖析.git目录结构
让我们再次进入之前创建的 gitcode目录,并使用 tree命令(如果未安装,可使用 sudo yum install tree或 sudo apt install tree安装)来查看 .git的目录结构。
liu@139-159-150-152:~/gitcode$ tree .git/ -L 2
.git/
├── branches
├── COMMIT_EDITMSG
├── config
├── description
├── HEAD
├── hooks
│ ├── applypatch-msg.sample
│ ├── commit-msg.sample
│ ├── fsmonitor-watchman.sample
│ ├── post-update.sample
│ ├── pre-applypatch.sample
│ ├── pre-commit.sample
│ ├── prepare-commit-msg.sample
│ ├── pre-push.sample
│ ├── pre-rebase.sample
│ ├── pre-receive.sample
│ └── update.sample
├── index
├── info
│ └── exclude
├── logs
│ ├── HEAD
│ └── refs
│ └── heads
│ └── master
├── objects
│ ├── 2d
│ │ └── 832d9044c6980818dc0508b4b8f2e7f84f0a9a
│ ├── 61
│ │ └── 422f8e7f3b7f3c9e5f3f7f3b7f3c9e5f3f7f3b7
│ ├── c6
│ │ └── 1428926f3853d4ec6dde904415b0e6c1dabcc6
│ ├── e6
│ │ └── 9de29bb2d1d6434b8b29ae775ad8c2e48c5391
│ ├── info
│ └── pack
└── refs
├── heads
│ └── master
└── tags
20 directories, 20 files
结构看起来有些复杂,但我们只需关注其中几个最核心的文件和目录:
-
HEAD文件 :这是一个指向当前所在分支的引用文件。通常它的内容是ref: refs/heads/master,表示当前处于master分支。你可以把它理解为一个"指南针",总是指向你正在工作的分支。 -
index文件 :这就是暂存区(Stage) 的实际物理存储位置。它是一个二进制文件,记录了当前有哪些文件被暂存,以及它们的内容哈希等信息。执行git add命令就是在修改这个文件。 -
objects目录 :这是Git的对象数据库 ,是Git版本控制的核心。所有被Git管理的内容(文件内容、目录树、提交信息等)都以"对象"的形式存储在这里。它是一个内容寻址文件系统,意味着每个对象由其内容的SHA-1哈希值来命名和查找。正是这个设计,保证了Git数据的完整性和高效性。-
Blob对象:存储文件的具体内容。
-
Tree对象:类似于目录,存储了指向Blob对象或其他Tree对象的指针,记录了目录结构。
-
Commit对象:存储一次提交的元数据,如作者、提交者、提交时间、提交信息,以及指向其父提交(一个或多个)和顶层Tree对象的指针。
-
-
refs目录 :存储**引用(References)** 的地方,可以理解为是"指针"或"标签"的集合。-
refs/heads/下存放着各个分支 的引用。例如,refs/heads/master文件里存储着master分支最新一次提交的SHA-1值。 -
refs/tags/下存放着标签的引用(用于标记特定的重要版本,如v1.0.0)。
-
-
config文件 :当前Git仓库的配置文件。这里保存的配置会覆盖全局(~/.gitconfig)的配置。 -
hooks目录 :存放钩子(Hooks)脚本的目录。钩子是在Git执行某些特定操作(如提交、推送等)前后自动触发的自定义脚本,可用于自动化任务,如代码风格检查、运行测试等。
一个简单的例子来串联这些概念:
当我们执行 git commit -m "first commit"时,Git会:
-
将暂存区(
index)中的内容生成一个Tree对象,存入objects。 -
创建一个Commit对象,指向这个Tree对象和父提交(首次提交没有父提交),存入
objects。 -
将当前分支(如
master)的引用(在refs/heads/master中)更新为这个新Commit对象的SHA-1值。
三、 管理文件修改
现在,我们回到实战层面。版本控制的核心是跟踪文件的变化 。让我们修改 ReadMe文件,并让Git记录这个过程。
-
修改工作区文件:
liu@139-159-150-152:~/gitcode$ vim ReadMe # 在第一行"hello world"后面添加一个感叹号,在第二行"hello git"后面也添加一个感叹号 liu@139-159-150-152:~/gitcode$ cat ReadMe hello world! hello git! -
查看仓库当前状态 (
git status):修改完成后,我们首先需要知道仓库当前的状态。
git status命令用于显示工作区和暂存区的状态,这是最常用的Git命令之一。liu@139-159-150-152:~/gitcode$ git status On branch master Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git restore <file>..." to discard changes in working directory) modified: ReadMe no changes added to commit (use "git add" and/or "git commit -a")git status命令输出了非常有价值的信息:-
On branch master:告诉你当前在master分支上。 -
Changes not staged for commit:"未被暂存以备提交的变更" 。这是关键!它列出了工作区中那些已经被Git跟踪过,但修改后尚未放入暂存区 的文件。这里显示ReadMe文件被修改了。 -
它还友好地给出了提示:使用
git add <file>...来暂存修改(即放入暂存区),或者使用git restore <file>来丢弃工作区的修改(将文件恢复到暂存区或版本库中的状态)。 -
no changes added to commit:暂存区是空的,没有可以提交的内容。
-
-
将修改添加到暂存区 (
git add):根据提示,我们需要将修改添加到暂存区。
liu@139-159-150-152:~/gitcode$ git add ReadMe再次运行
git status查看状态:liu@139-159-150-152:~/gitcode$ git status On branch master Changes to be committed: (use "git restore --staged <file>..." to unstage) modified: ReadMe状态变了!现在
ReadMe文件位于Changes to be committed区域下,表示修改已经被暂存,准备提交。 -
提交修改 (
git commit):将暂存区的修改提交到版本库。
liu@139-159-150-152:~/gitcode$ git commit -m "add exclamation mark to ReadMe" [master 926f385] add exclamation mark to ReadMe 1 file changed, 2 insertions(+), 2 deletions(-)注意提交信息的描述,要能清晰反映本次修改的目的。
-
再次查看状态:
liu@139-159-150-152:~/gitcode$ git status On branch master nothing to commit, working tree clean非常棒!
working tree clean表示工作区是干净的,没有任何未暂存或未提交的修改。所有改动都已记录在版本库中。
四、 查看文件修改差异
git status只能告诉我们哪些文件被修改了,但具体修改了什么内容,我们需要 git diff命令。
git diff是一个功能强大的命令,用于比较不同版本、不同分支、工作区、暂存区之间的差异。
场景一:工作区与暂存区的差异
当我们修改了文件,但还没有执行 git add时,可以使用 git diff来查看工作区文件 和暂存区文件的差异。
-
再次修改
ReadMe文件:liu@139-159-150-152:~/gitcode$ vim ReadMe # 在文件末尾新增一行 "I am learning git." liu@139-159-150-152:~/gitcode$ cat ReadMe hello world! hello git! I am learning git. -
使用
git diff查看未暂存的修改:liu@139-159-150-152:~/gitcode$ git diff diff --git a/ReadMe b/ReadMe index 8e56c2b..7c7a6c3 100644 --- a/ReadMe +++ b/ReadMe @@ -1,2 +1,3 @@ hello world! hello git! +I am learning git.让我们解析这个输出:
-
--- a/ReadMe和+++ b/ReadMe:表示比较的是a版本(通常是旧版本/暂存区版本)和b版本(通常为新版本/工作区版本)的ReadMe文件。 -
@@ -1,2 +1,3 @@:表示在旧版本中,比较范围是从第1行开始的2行;在新版本中,比较范围是从第1行开始的3行。 -
以
-开头的行(红色,在终端中通常显示为红色)表示在旧版本中存在,但在新版本中被删除的行(本例中没有)。 -
以
+开头的行(绿色,在终端中通常显示为绿色)表示在新版本中存在,但在旧版本中不存在的行。这里显示新增了+I am learning git.这一行。
-
场景二:暂存区与版本库的差异
当我们已经执行了 git add将修改放入暂存区,但还没有提交时,可以使用 git diff --cached(或 git diff --staged,这是更现代的名称)来查看暂存区文件 和版本库中最后一次提交的差异。
-
将刚才的修改添加到暂存区:
liu@139-159-150-152:~/gitcode$ git add ReadMe -
查看暂存区与版本库的差异:
liu@139-159-150-152:~/gitcode$ git diff --cached diff --git a/ReadMe b/ReadMe index 8e56c2b..7c7a6c3 100644 --- a/ReadMe +++ b/ReadMe @@ -1,2 +1,3 @@ hello world! hello git! +I am learning git.输出与之前类似,因为这次比较的是"暂存区(新)"和"版本库最新提交(旧)"。
场景三:查看两次提交之间的差异
我们可以通过 git diff <commit-id1> <commit-id2>来比较任意两次提交之间的差异。<commit-id>可以是完整的SHA-1哈希值,也可以是它的前几位(通常6-8位就足够唯一标识了)。
-
首先查看提交历史,获取提交ID:
liu@139-159-150-152:~/gitcode$ git log --oneline 926f385 (HEAD -> master) add exclamation mark to ReadMe c614289 commit my first file -
比较首次提交和第二次提交的差异:
liu@139-159-150-152:~/gitcode$ git diff c614289 926f385 diff --git a/ReadMe b/ReadMe index 61f422f..8e56c2b 100644 --- a/ReadMe +++ b/ReadMe @@ -1,2 +1,2 @@ -hello world -hello git +hello world! +hello git!输出显示,第一次提交(
c614289)中的两行没有感叹号,而第二次提交(926f385)中这两行都加上了感叹号。
五、 总结与回顾
本篇我们深入了两个重要的方面:
-
Git的内部机理 :我们打开了
.git目录这个"黑盒",初步了解了HEAD、index、objects、refs等核心组件的作用。这有助于我们理解Git的强大和高效是如何实现的。 -
文件修改的完整工作流:我们实践了修改文件的标准流程:
-
修改文件(在工作区)。
-
使用
git status查看状态,确认哪些文件被修改。 -
使用
git diff查看工作区与暂存区的详细修改内容。 -
使用
git add将修改从工作区移动到暂存区。 -
再次使用
git status查看状态,确认修改已暂存。 -
使用
git diff --cached查看暂存区与版本库的差异。 -
使用
git commit将修改从暂存区永久提交到版本库。 -
最终
git status显示工作区干净。
-
这是Git最基本的、也是最核心的单人开发工作流。掌握好 git status和 git diff这两个状态查看和差异分析工具,将使你清楚地知道每一步操作的结果,从而能更自信、更准确地使用Git。
在下一篇博客中,我们将探索Git的"时光机"功能:如何进行版本回退、撤销修改,以及删除被Git管理的文件。