

在现代软件开发中,版本控制系统是不可或缺的基石。Git 作为目前最主流的分布式版本控制工具,其强大的分支管理、历史追溯以及协作能力,极大地提升了开发效率。本文将基于实际操作流程,深入剖析 Git 从仓库初始化、配置管理、核心区域交互到版本回退与文件操作的完整链路,并重点探讨其背后的工作原理。
在开始一个项目时,通常面临两种情况:一种是全新的项目,从零开始;另一种是接手现有的项目。Git 提供了 git init 和 git clone 两种指令来应对这两种场景。
本地初始化:从零开始
当在本地创建一个新的文件夹,或者基于已有的本地代码想要纳入版本控制时,使用初始化命令是第一步。
在目标目录下执行:
git init执行该指令后,Git 会立刻给出反馈,表明已在当前路径下初始化了一个空的 Git 仓库。

此时,虽然文件夹看起来没有什么变化,但如果开启隐藏文件显示,会发现当前目录下多了一个名为 .git 的隐藏目录。这个目录是 Git 的核心,它记录了版本库的所有信息。
git init 与 git clone 的深度对比
虽然两者都能获得一个 Git 仓库,但在底层逻辑和后续操作上有着本质区别。
.git 目录的空壳。git remote add origin <url> 来建立连接。git clone 在多人协作中更加高效,因为它省去了配置远程连接的步骤。origin 的远程连接,在有权限的情况下,可以直接执行 git pull 或 git push。.git 目录结构理解 .git 目录是理解 Git 原理的关键。执行 git init 后,生成的这个隐藏目录并非黑盒,我们可以通过 tree 命令来一探究竟。
指令:
tree .git/
如上图所示,.git 目录包含了一系列核心组件,它们共同支撑起了 Git 的版本控制功能:
.gitignore 文件中(以免提交给其他人),就可以写在这里。在使用 Git 提交代码之前,必须先自报家门。Git 需要知道“是谁”做了这次修改,以便在历史记录中进行追溯。
可以在当前仓库下单独配置用户名和邮箱,这种配置的优先级高于全局配置,但仅对当前仓库有效。
git config --global user.name 'yummy小二'
git config --global user.email '934679045@qq.com'注:上述命令虽然加了 --global,但在实际针对特定仓库配置时,应去掉 --global 参数。根据提供的笔记内容,这里展示的是全局配置的命令格式,但如果要在.git/config中生效,则不加 global。
配置信息会存储在当前项目的 .git/config 文件中。

如果需要删除本地配置,可以使用 --unset 参数:
git config --unset user.name
git config --unset user.email在大多数开发场景下,我们希望所有仓库都使用同一套身份信息,这时就需要使用全局配置。
git config --global user.name "yummy小二"
git config --global user.email "934679045@qq.com"全局配置信息存储在用户主目录下的 .gitconfig 文件中(例如 Linux 或 macOS 下的 ~/.gitconfig)。

需要注意的是,针对全局配置项的删除,必须严格同时指定 --global 和 --unset 参数。如果仅使用 --unset 而忽略 --global,Git 会默认尝试在本地仓库配置中查找并删除该项,导致操作失败或不符合预期。
git config --global --unset user.name理解 Git 的三个工作区域是掌握其所有操作逻辑的前提。Git 将文件的生命周期划分为三个区域:工作区(Working Directory)、暂存区(Stage/Index) 和 版本库(Repository)。
这是我们在电脑文件管理器里能看到的目录,也是开发人员实际编辑代码的地方。
首先在仓库中创建一个名为 README.md (自述文件) 的文件。此时,该文件仅存在于文件系统中,Git 尚未对其进行跟踪管理,它仅仅是磁盘上的一个普通文件。

暂存区是 Git 架构中最为精妙的设计之一。它充当了工作区与版本库之间的缓冲区。
暂存区并不保存文件的完整内容,而是存储修改对象的索引。这意味着,当你把文件加入暂存区时,Git 实际上是先生成了一个对象存储文件内容,然后在暂存区的列表里记下了这个对象的指纹(哈希值)。

上图清晰地展示了 Git 的三层流转架构:
git add 命令,工作区的修改被写入对象库,同时暂存区更新索引以指向这些新对象。git commit 命令,暂存区的索引树被提交到 master 分支,形成永久的历史记录。将代码保存到版本库需要两步走:
第一步:使用 git add 将修改添加到暂存区。
git add .这一步会将文件的快照写入 .git/objects 目录,并更新 .git/index 文件。
第二步:使用 git commit 将暂存区的内容提交到当前分支(通常是 master)。
git commit -m "对提交文件的描述信息"这里的 -m 参数用于添加提交说明(Commit Message)。这是版本回溯时的重要依据,就相当于是开发日志,记录了每次修改的目的。如果不写清楚,后续回顾历史时将寸步难行。

随着提交次数的增加,查看历史记录和分析文件状态变得必不可少。
使用 git log 可以查看版本库的提交历史。

日志信息包含以下关键要素:
为了获取更简洁的视图,可以使用 --pretty=oneline 参数,将每条日志压缩为一行显示:
git log --pretty=oneline
这种格式极大地节省了屏幕空间,便于快速浏览大量的版本历史。
当执行 git add 后,.git 目录下会生成或更新 index 文件。

.git 目录下的文件列表中,index 文件即为暂存区的实体。这再次印证了 Git 追踪管理的核心在于“修改”而非“文件”本身。每一次修改被 add 后,都会生成一个新的 Git 对象,Git 通过索引指向这些对象来构建文件系统的快照。
在开发过程中,经常需要确认工作区所做的修改与暂存区或版本库中的内容有何不同。git diff 命令便是用于此目的。我们将代码上传到版本库后,通过 diff 可以清晰地看到每一行代码的增删变动,这对于代码审查(Code Review)至关重要。

同时,git status 命令是日常开发中使用频率最高的命令之一,用于查看工作区、暂存区与本地仓库之间的状态同步情况。
git status
通过 git status,我们可以随时知道哪些文件被修改了但还没暂存,哪些文件已暂存但还没提交。
版本回退是 Git 最强大的功能之一,允许开发者在历史版本之间自由穿梭,提供了极大的容错空间。其核心命令是 git reset。
git reset 的本质是移动 HEAD 指针,并根据参数决定是否更新暂存区和工作区。HEAD 指向当前分支的最新提交,通过改变 HEAD 的指向,即可实现版本库的回退。
该命令有三个核心参数,对应不同的回退深度:
git reset [--soft | --mixed | --hard] [HEAD]为了理解这三个参数,我们需要建立一个概念模型:
工作区 (Working Dir) | 暂存区 (Staging Area) | 版本库 (Repository) | 参数选项 | 深度解析 |
|---|---|---|---|---|
git world | git world | git world | (起始状态) | 三个区域内容一致,通常是刚 commit 完的状态。 |
git world | git world | git |
| 软重置。仅回退版本库的 HEAD 指针。暂存区和工作区保留当前修改。这意味着刚才提交的内容回到了“已 add 但未 commit”的状态。常用于修正上一次提交的 Message 或合并多个提交。 |
git world | git | git |
| 混合重置(默认)。回退版本库和暂存区。仅保留工作区的修改。这意味着刚才提交的内容回到了“已修改但未 add”的状态。 |
git | git | git |
| 硬重置。彻底回退。版本库、暂存区、工作区全部还原为旧版本。这是毁灭性的操作,工作区中未提交的代码将永久丢失,无法找回。使用前必须确认当前工作区无重要未备份代码。 |
假设当前 README.md 经历了多次修改和提交,存在两个版本。通过 git log 查看当前的提交历史,可见存在两次提交记录。现在需要回退到上一个版本。
执行硬重置命令,指定目标版本的 Commit ID(或使用 HEAD^):

git reset --hard 4de2ec06ec660056f50a1efd1778cec2f5ffbf8d执行后,再次查看状态:

HEAD 已经指向了指定的提交,系统提示 HEAD is now at...。
这里需要深入理解的是,虽然我们常说“回退”,但 Git 的本质是指针移动。git reset 实际上是让 HEAD 指针指向了旧的 Commit 对象,同时根据参数(如 --hard)强制刷新了暂存区和工作区,使其与该 Commit 的内容保持一致。
如果执行了 git reset --hard 后发现“回退错了”,普通的 git log 已经无法查看到那个被丢弃的 Commit ID 了。此时,Git 提供了一剂“后悔药” —— git reflog。
git reflog 记录了 HEAD 指针的所有移动历史,包括 reset、commit、checkout 等操作。它保留了操作的短哈希值。

如上图所示,可以看见 HEAD 曾经指向的位置。只要找到那个“未来的版本”的哈希值,依然可以执行 git reset --hard 跳回去,从而实现“反悔”。这说明 Git 的版本回退在本质上是通过哈希值在时间轴上的任意跳转。
在开发过程中,针对不同阶段的错误修改,Git 提供了不同层级的撤销方案。
当修改了文件但尚未执行 git add 时,文件修改仅存在于工作区。
例如,对 README.md 进行了一行错误的修改。若要丢弃该修改,恢复到最近一次 add 或 commit 的状态,使用以下命令:
git checkout -- README.md
执行该命令意味着,之前的修改彻底消失了。这主要用于拉取了版本库代码后,在本地进行调试或尝试性修改,最终发现思路错误,想要一键还原的场景。一般建议最好另外开启一个分支或文件夹进行实验,而不是直接在主项目上操作。
注意:-- 后面需要有空格,这是为了防止文件名与分支名混淆。
当修改文件后已经执行了 git add,此时修改已进入暂存区。要撤销需要分两步走。
第一步:将文件从暂存区移出,放回工作区。使用默认的 git reset(相当于 --mixed):
git reset HEAD AIGameDemo.cppHEAD 表示当前版本。如果要回退到上一个版本使用 HEAD^,上两个版本使用 HEAD^^。
执行后,暂存区被清空,修改回到了工作区。

状态变为 Changes not staged for commit,验证了撤销暂存区的操作成功。
第二步:继续处理工作区的残留修改,将其彻底丢弃:
git checkout -- AIGameDemo.cpp此时工作区也恢复干净,所有修改均已被撤销。
当修改已经 commit 到了本地版本库,但尚未 push 到远程仓库时,可以通过版本回退来撤销本次提交。
首先制造一个已提交的状态:修改文件 -> add -> commit。

此时三个区域都包含该修改。为了彻底消除这次提交的影响,使用 --hard 模式:
git reset --hard HEAD^HEAD^ 指向当前版本的父版本(上一个版本)。

必须再次强调,前提是该提交尚未推送到远程仓库。 如果代码已经推送到远程,使用 reset 强行回退并强制推送(force push)可能会导致多人协作时的版本冲突,造成严重的团队事故。
在 Git 中,删除文件不仅仅是删除磁盘上的文件,还需要更新暂存区和版本库的记录,这是一个完整的事务。
假设仓库中存在 file1 文件。如果在文件管理器或使用系统命令 rm 删除了该文件,Git 会检测到工作区的变化。

上图显示 file 处于 deleted 状态,但这仅仅是工作区的变动。为了确认删除,需要执行 git add 将删除操作暂存:
git add file此时 Git 知道这个文件被删除了。最后提交到版本库:
git commit -m "delete file"这样才算完成了一次完整的删除提交。
git rmGit 提供了 git rm 命令,将上述的“删除工作区文件”和“暂存删除操作”合并为一步。
git rm file执行该命令后,文件会直接从磁盘删除,并且该删除操作会被立即放入暂存区。执行后无需再次 git add,直接进行 git commit 即可完成整个删除流程。这种方式更加简洁高效,是管理文件删除的推荐做法。
如果是不小心删错了文件,想要找回来,可以使用 restore 命令(这是新版 Git 推荐用来替代 checkout 处理文件的命令)。
情况 A:文件仅在工作区被删,尚未 add
git restore file这将从暂存区(index)恢复文件到工作区。
情况 B:文件已被删,且已 add 到暂存区
此时查看状态,会显示绿色的 deleted: file。
我们需要先将这个“删除操作”从暂存区撤回:
git restore --staged file执行完这句后,文件状态变回工作区被删(红色的 deleted)。接着再执行:
git restore file文件就会奇迹般地回到文件夹中。这里体现了 Git 对文件状态的精细控制能力。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。