【Git】《Git 系列指南(二):Git基本操作与 reset 三种模式》

本篇文章从零开始,带你完成 Git 的安装与配置,深入理解工作区、暂存区、版本库三个核心概念,并掌握添加、修改、回退、撤销、删除等 Git 基本操作,为后续深入学习打下坚实基础。


Git 基本操作

1.1创建 Git 本地仓库

要提前说的是,仓库是进⾏版本控制的⼀个⽂件⽬录 。我们要想对⽂件进⾏版本控制,就必须先创建⼀个仓库出来。创建⼀个 Git 本地仓库对应的命令为 git init,注意命令要在⽂件⽬录下执⾏,例如:

我们发现,当前⽬录下多了⼀个 .git 的隐藏⽂件, .git ⽬录是 Git 来跟踪管理仓库的,不要⼿动
修改这个⽬录⾥⾯的⽂件,不然改乱了,就把 Git 仓库给破坏了。其中包含 Git 仓库的诸多细节,

.git 目录的核心组成,用这个表说明情况,

文件/目录 一句话说明
objects/ 存放所有历史版本的文件内容(最核心)
refs/ 记录分支和标签指向哪个版本
HEAD 标记你当前在哪个分支上
config 当前仓库的配置(用户名、远程地址等)
hooks/ 某些操作时自动执行的脚本(如提交前自动检查)

日常开发时,谁在服务谁

Git 命令 读/写了 .git 的哪个部分
git commit 新增 objects/,更新 refs/heads/(分支最新指向)
git checkout refs/heads/,改 HEAD(切换分支)
git branch 增删 refs/heads/ 中的分支文件
git config 读写 config
git push objects/ + refs/,发送给远程仓库

1.2配置 Git

当安装 Git 后⾸先要做的事情是设置你的 ⽤⼾名称 和 e-mail 地址,这是⾮常重要的。配置命令为:

cpp 复制代码
git config [--global] user.name "Your Name"
git config [--global] user.email "email@example.com"
# 把 Your Name 改成你的昵称
# 把 email@example.com 改成邮箱的格式,只要格式正确即可。

其中 --global 是⼀个可选项。 如果使⽤了该选项,表⽰这台机器上所有的 Git 仓库都会使⽤这个配置。如果你希望在不同仓库中使⽤不同的 name 或 e-mail ,可以不要 --global 选项,但要注意的是,执⾏命令时必须要在仓库⾥


查看配置命令为:

cpp 复制代码
 git config -l

删除对应的配置命令为:

cpp 复制代码
git config --global --unset user.name
git config --global --unset user.email

1.3⼯作区、暂存区、版本库

工作区(Working Directory):即你电脑上存放项目代码和文件的实际目录,也就是你能直接看到和编辑的那些文件所在的文件夹。你在 IDE 中打开、修改、保存的文件,都位于工作区中。

暂存区(Stage / Index):也叫索引(Index),位于 .git/index 文件中。它是一个临时存储区域,用于记录你下一次要提交的内容。当你执行 git add 时,文件的快照就会被写入暂存区。

版本库(Repository):即项目根目录下的 .git 隐藏文件夹。它不算工作区,而是 Git 的"数据仓库"。版本库中存储了所有提交历史、分支信息、标签以及完整的对象数据库(objects/)。只要 .git 目录完好无损,整个项目的所有历史版本就都还在。


三者之间的关系与数据流转

cpp 复制代码
工作区 (Working Directory)  ── git add ──>  暂存区 (Stage / Index)  ── git commit ──>  版本库 (Repository / .git)

简单来说:工作区 是你干活的地方,暂存区 是你整理准备提交的地方,版本库是 Git 永久保存所有历史版本的地方。

我们用一张图来直观理解:

区域 对应位置 核心命令
工作区 项目目录(不含 .git git status 查看修改
暂存区 .git/index git add 写入暂存区
版本库 .git/ git commit 永久保存

补充:三个区域的核心区别

对比维度 工作区 暂存区 版本库
位置 项目根目录(不含 .git .git/index .git/ 目录
内容来源 你自己修改编辑的文件 git add 添加的快照 git commit 提交的对象
是否被 Git 追踪 部分可能未被追踪 即将被提交 已永久保存
误操作是否可恢复 未追踪的文件丢失较难恢复 可用 git restore --staged 撤回 几乎永久可恢复

补充:常用场景对应关系(这个里面的有些格式后面我会慢慢一一讲解)

场景 涉及区域 命令
修改了一个文件 工作区 直接在 IDE 或编辑器中修改
准备提交某个文件 工作区 → 暂存区 git add 文件名
把所有修改都准备提交 工作区 → 暂存区 git add .
正式提交保存 暂存区 → 版本库 git commit -m "信息"
查看当前状态 三个区域对比 git status
查看工作区与暂存区的差异 工作区 vs 暂存区 git diff
查看暂存区与版本库的差异 暂存区 vs 版本库 git diff --cached
撤销工作区的修改 工作区 git restore 文件名
撤销暂存区的添加 暂存区 git restore --staged 文件名

下⾯这个图展⽰了⼯作区、暂存区和版本库之间的关系:

图片补充:

概念 准确含义
HEAD 指向当前所在分支的指针。例如 HEAD 指向 master,说明你在 master 分支上。它本质上是一个引用,而不是一个独立的数据存储区域。
master 分支名,指向该分支最新的一次提交。当你在 master 分支上执行 git commit 时,master 指针会自动更新,指向新提交。
暂存区(Stage) 本质上是一个文件列表,记录了下一次 commit 要包含哪些文件的哪些版本,位于 .git/index
版本库(Repository) 就是 .git 目录本身,包含了 objects、refs、HEAD、config 等全部 Git 元数据。
  • 图中左侧为⼯作区,右侧为版本库。Git 的版本库⾥存了很多东西,其中最重要的就是暂存区。
  • 在创建 Git 版本库时,Git 会为我们⾃动创建⼀个唯⼀的 master 分⽀,以及指向 master 的⼀个指针叫 HEAD。(分⽀和HEAD的概念后⾯再说)
  • 当对⼯作区修改(或新增)的⽂件执⾏ git add 命令时,暂存区⽬录树的⽂件索引会被更新。
  • 当执⾏提交操作 git commit 时,master 分⽀会做相应的更新,可以简单理解为暂存区的⽬录树才会被真正写到版本库中。

由上述描述我们便能得知:通过新建或粘贴进⽬录的⽂件,并不能称之为向仓库中新增⽂件,⽽只是在⼯作区新增了⽂件。必须要通过使⽤ git add 和 git commit 命令才能将⽂件添加到仓库中进⾏管理!!


1.4添加⽂件--场景⼀

在包含 .git 的⽬录下新建⼀个 ReadMe ⽂件,我们可以使⽤ git add 命令可以将⽂件添加到暂存
区:

  • 添加⼀个或多个⽂件到暂存区: git add file1 file2 ...
  • 添加指定⽬录到暂存区,包括⼦⽬录: git add dir
  • 添加当前⽬录下的所有⽂件改动到暂存区: git add .

再使⽤ git commit 命令将暂存区内容添加到本地仓库中:

  • 提交暂存区全部内容到本地仓库中: git commit -m "message"
  • 提交暂存区的指定⽂件到仓库区: git commit file1 file2 ... -m "message"

注意 git commit 后⾯的 -m 选项,要跟上描述本次提交的 message,由⽤⼾⾃⼰完成,这部分内
容绝对不能省略,并要好好描述,是⽤来记录你的提交细节,是给我们⼈看的。

例如:

git commit 命令执⾏成功后会告诉我们,1个⽂件被改动(就是我们新添加的ReadMe⽂件),插
⼊了两⾏内容(ReadMe有两⾏内容)。

我们还可以多次 add 不同的⽂件,⽽只 commit ⼀次便可以提交所有⽂件,是因为需要提交的⽂件是通通被 add 到暂存区中,然后⼀次性 commit 暂存区的所有修改。如:


截⾄⽬前为⽌,我们已经更够将代码直接提交⾄本地仓库了。我们可以使⽤ git log 命令,来查看

下历史提交记录:

bash 复制代码
git log	显示完整提交历史

命令 :git log

作用 :查看当前分支的提交历史

输出内容 :commit哈希值、Author、Date、提交注释

通过 git log 可以查看当前仓库的完整提交历史,每条记录都包含一个由 Git 自动生成的 SHA-1 哈希值(40位十六进制字符串)作为提交的唯一标识,以及提交的作者、时间和注释信息。所有提交按时间倒序排列,最近的提交显示在最上面。图片中显示该仓库当前在 master 分支上,共有 2 次提交记录:最早的一次是 first file,最近的一次是 add 3 files,最新提交的哈希值为 9f0865d...。日常使用中,推荐使用 git log --oneline 以更简洁的方式查看提交历史,每个提交只显示哈希值的前 7 位和注释信息,更加清爽易读。


该命令显⽰从最近到最远的提交⽇志,并且可以看到我们 commit 时的⽇志消息。

如果嫌输出信息太多,看得眼花缭乱的,可以试试加上**--pretty=oneline 参数:**

git log --oneline 精简显示,每行一个提交(最常用)


查看 .git ⽂件

先来看看我们的 .git 的⽬录结构:

需要注意的地方

现象 说明
objects/ 里的目录名是 2位十六进制(如 9f/e7/ 这就是 commit 对象哈希值的前两位
index 文件出现 说明暂存区有内容(你执行过 git add
logs/ 目录出现 Git 开始记录操作日志,方便 git reflog 恢复历史
COMMIT_EDITMSG 文件 是你最后一次 commit 时写的提交信息,临时文件
refs/heads/master 文件 记录 master 分支当前指向哪个 commit
HEAD 文件 记录当前在哪个分支上

通过**tree .git/**可以清晰地看到 Git 版本库在磁盘上的物理存储结构。

所有 Git 对象(commit、tree、blob)都存储在**.git/objects/**目录下,每个对象以其完整 40 位 SHA-1 哈希值的前 2 位作为子目录名、后 38 位作为文件名进行存放。

图片中出现的 e7/9f/ 目录分别对应两次提交的 commit 对象,而 30/39/d3/e6/ 等目录则对应 tree 对象和 blob 对象,它们是记录目录结构和文件实际内容的载体。

此外,.git/ 目录中的 index 文件代表暂存区的内容,logs/ 目录记录操作日志以支持 git reflog 恢复历史,refs/heads/master 文件记录**master** 分支当前指向的提交,HEAD 文件则标记当前所在的分支。

本质上,.git/ 目录就是 Git 仓库的完整数据库,每次 git commit 都会在其中新增至少 3 个对象,确保所有历史版本都得到完整保存。


1. index就是我们的暂存区,add 后的内容都是添加到这⾥的。

2.HEAD 就是我们的默认指向 master 分⽀的指针:

⽽默认的 master 分⽀,其实就是:

9f0865dc0a82c076237e642e8da035d7e7056cea是什么东西呢?保存的就是当前最新
的 commit id

3. objects为 Git 的对象库,⾥⾯包含了创建的各种版本库对象及内容。当执⾏ git add 命令

时,暂存区的⽬录树被更新,同时⼯作区修改(或新增)的⽂件内容被写⼊到对象库中的⼀个新的对象中,就位于 ".git/objects" ⽬录下,让我们来看看这些对象有何⽤处:


三种基本用法

命令 含义
git cat-file -t 对象哈希 查看对象的类型(commit / tree / blob / tag)
git cat-file -s 对象哈希 查看对象的大小(字节数)
git cat-file -p 对象哈希 查看对象的内容(美化打印)

其中 -p 就是你刚才想用的,意为 pretty-print(美化打印)。

查找 object 时要将 commit id 分成2部分,其前2位是⽂件夹名称,后38位是⽂件名称。

找到这个⽂件之后,⼀般不能直接看到⾥⾯是什么,该类⽂件是经过 sha (安全哈希算法)加密过的⽂件,好在我们可以使⽤ git cat-file 命令来查看版本库对象的内容:

这就是我们最近⼀次的提交

解释如下:

行内容 名词 含义
tree 30e20ee... tree(目录树对象) 记录这次提交里有哪些文件、文件名是什么、分别对应哪个 blob 对象,相当于一个目录快照
parent e747883... parent(父提交) 指向这次提交的上一次提交,把两次提交串成一条历史链。第一次提交没有这一行
author hjq <...> 1784817876 +0800 author(作者) 这个文件的原始作者 是谁,以及提交的时间
committer hjq <...> 1784817876 +0800 committer(提交者) 把这次提交录入到仓库的人。通常和 author 一样,只会在打补丁、转交提交等场景下不同
add 3 files commit message(提交注释) 你写的那句提交说明,git commit -m "这里的内容"

Git 会自动给时间戳加上时区,你这里的 +0800 表示东八区,也就是北京时间。author 和 committer 一般都是同一个人,不会特意区分。但在开源项目里(比如 Linux 内核),如果一个人写补丁、另一个人负责合入,那这两个字段就会不同:author 是原作者,committer 是合入者。


这就是第一次提交 first file 的信息,相关解释如下:

其中,还有⼀⾏ tree 30e20ee76836a7edd4b370121927bc6668ecc3d5 **,我们使⽤同样的⽅
法,看看结果,**继续深入:查看 tree 对象

在看 ReadMe 对应 390ca7a5098bcfa36a43dbf8ba5aa206dfa092ee:

这是我们对ReadMe做的修改,被git记录了下来


总结⼀下,在本地的 git 仓库中,有⼏个⽂件或者⽬录很特殊

  • index: 暂存区, git add 后会更新该内容。
  • HEAD: 默认指向 master 分⽀的⼀个指针。
  • refs/heads/master: ⽂件⾥保存当前 master 分⽀的最新 commit id 。
  • objects: 包含了创建的各种版本库对象及内容,可以简单理解为放了 git 维护的所有修改。

1.5添加⽂件--场景⼆

和大家学习到这⾥,我们已经清楚了如何向仓库中添加⽂件,并且对于⼯作区、暂存区、版本库也有了⼀定的认识。那么我们再展⽰⼀种添加⽂件的场景,能加深对⼯作区、暂存区、版本库的理解,⽰例如下:

提交后发现打印了 1 file changed, 0 insertions(+), 0 deletions(-) ,意思是只
有⼀个⽂件改变了,这时我们提出了疑问,不是新增了两个⽂件吗?

再来回忆下, git add 是将⽂件添加到暂存区, git commit 是将暂存区的内容添加到本地仓库中。由于我们并没有使⽤ git add file5 ,file5 就不在暂存区中维护,所以我们 commit 的时候其实只是把已经在暂存区的 file4 提交了,⽽遗漏了⼯作区的 file5。

如何提交 file5 呢?

很简单,再次add , commit 即可。


修改⽂件

Git ⽐其他版本控制系统设计得优秀,因为 Git 跟踪并管理的是修改,⽽⾮⽂件。

什么是修改?⽐如你新增了⼀⾏,这就是⼀个修改,删除了⼀⾏,也是⼀个修改,更改了某些字符,也是⼀个修改,删了⼀些⼜加了⼀些,也是⼀个修改,甚⾄创建⼀个新⽂件,也算⼀个修改。

让我们将 ReadMe ⽂件进⾏⼀次修改:常用选项速查


此时,仓库中的 ReadMe 和我们⼯作区的 ReadMe 是不同的,如何查看当前仓库的状态呢?

git status 命令⽤于查看在你上次提交之后是否有对⽂件进⾏再次修改。

上⾯的结果告诉我们,ReadMe 被修改过了,但还没有完成添加与提交。⽬前,我们只知道⽂件被修改了,如果能知道具体哪些地⽅被修改了,就更好了。有人会说,我刚改的我知道呀!可是,你还记得你三天前写了什么代码吗?或者没写?

cpp 复制代码
diff --git a/ReadMe b/ReadMe
a/ReadMe:旧版本(暂存区里的版本)
b/ReadMe:新版本(工作区里的版本)


index 390ca7a..2a33abb 100644
390ca7a:旧版本(暂存区)的 blob 哈希值(前7位)

2a33abb:新版本(工作区)的 blob 哈希值(前7位)

100644:文件权限(普通文件)


--- a/ReadMe
+++ b/ReadMe
---:旧文件(暂存区)

+++:新文件(工作区)


@@ -1,3 +1,5 @@
-1,3:旧文件从第 1 行开始,共 3 行

+1,5:新文件从第 1 行开始,共 5 行


 hello git

 hello git
+
+hello world
没有 - 号的行:新旧版本共有的内容(hello git)

+ 号的行:新增的内容(hello world)

**git diff file 命令⽤来显⽰暂存区和⼯作区⽂件的差异,显⽰的格式正是Unix通⽤的diff格式。**也可以使⽤ git diff HEAD -- file 命令来查看版本库和⼯作区⽂件的区别。知道了对 ReadMe 做了什么修改后,再把它提交到本地仓库就放⼼多了。


git add 之后,就没有看到上⾯ no changes added to commit (use "git add"and/or "git commit -a") 的消息了。接下来让我们继续 git commit 即可:

master 分支上提交成功,提交的哈希值是 e87a95c,有 1 个文件被修改,新增了 2 行内容

我现在在 master 分支上,file5 是一个未被追踪的新文件(从未被 git add 过)

除了 file5 之外,没有其他需要提交的内容


补充:file5 需要提交吗?

根据我的实际需求,有三种处理方式:

需要提交 file5 :执行 git add file5 将 file5 添加到暂存区,再执行 git commit -m "add file5" 提交即可。

不需要提交 file5 :暂时不管它,它会一直显示在 git status 的未追踪文件列表中;如果确定不需要这个文件,可以直接删除:rm file5

以后再说:保持现状,file5 仍然是一个未被追踪的文件,不会影响其他操作。下次提交时再决定是否要把它纳入版本管理。
**核心原则:**Git 只会提交被 git add 过的文件。工作区中未被追踪的文件不会随 git commit 被保存到版本库中


总结一下:

git add 之后,暂存区就有了内容,git status 不会再提示"no changes added to commit";git commit 把暂存区的内容提交到了版本库,但 file5 没有被 git add,所以它还在工作区,不会随本次提交被保存。


1.6版本回退

之前我们也提到过,Git 能够管理⽂件的历史版本,这也是版本控制器重要的能⼒。如果有⼀天你发现之前前的⼯作做的出现了很⼤的问题,需要在某个特定的历史版本重新开始,这个时候,就需要版本回退的功能了。

执行 git reset 命令用于回退版本,可以指定退回到某一次提交。需要理解的是,"回退"本质上只是将版本库中的内容进行回退,而工作区或暂存区是否同步回退,则由命令参数决定。

git reset 的命令语法格式为:

cpp 复制代码
git reset [--soft | --mixed | --hard] [HEAD]

三种参数的区别:

  • --mixed (默认选项,可不带该参数):将暂存区 回退到指定版本,工作区 文件保持不变。适用于想撤销 git add 但又不想丢掉工作区改动的场景。

  • --soft :工作区和暂存区的内容都不变,只将版本库回退到指定版本。适用于想重新调整提交信息或合并多次提交的场景。

  • --hard :将暂存区和工作区都回退到指定版本。使用前务必确认工作区没有未提交的代码,因为该参数会丢弃工作区的所有修改且不可恢复,请谨慎使用。

HEAD 的写法说明:

  • 直接写 commit id:表示回退到指定的某次提交

  • HEAD:当前版本

  • HEAD^:上一个版本

  • HEAD^^:上上一个版本(以此类推,^ 的个数代表回退的步数)

也可以用 ~ 加数字表示:

  • HEAD~0HEAD~:当前版本

  • HEAD~1:上一个版本

  • HEAD~2:上上一个版本

  • 以此类推

使用建议: 在日常开发中,--soft 适合合并提交,--mixed 适合撤销暂存,--hard 仅在确认丢弃所有本地改动时使用。如果不确定,建议先用 git statusgit diff 查看当前状态,避免误操作。


为了便于表述,⽅便测试回退功能,我们先做⼀些准备⼯作:更新3个版本的 ReadMe,并分别进⾏3次提交,如下所⽰:

第⼀次修改提交

master:在 master 分支上提交

87e130d:本次提交的哈希值(前7位)

1 file changed:有 1 个文件被修改

2 insertions(+):新增了 2 行内容


第⼆次修改提交


第三次修改提交


查看历史提交记录

现在,如果我们在提交完 version3 后, 发现 version 3 编写错误,想回退到 version2,重新基于

version 2 开始编写。由于我们在这⾥希望的是将⼯作区的内容也回退到 version 2 版本,所以需

要⽤到 --hard 参数,⽰例如下:


我们惊奇的发现,此时 ReadMe ⽂件的内容,已经回退到 version2 了!,当前,我们再次⽤ git

log 查看⼀下提交⽇志,发现 HEAD 指向了version2,如下所⽰:

到这⾥⼀般回退功能就演⽰完了,但现在如果我后悔了,想再回到 version 3 怎么办?我们可以继续使⽤git reset 命令,回退到 version 3 版本,但我们必须要拿到 version 3 的 commit
id 去指定回退的版本。


但我们看到了 git log 并不能打印出 version 3 的 commit id ,运⽓好的话我们可以从终端

上去找找之前的记录,运⽓不好的话 commit id 已经被我们搞丢了。

但是,**Git 还提供了⼀个 git reflog 命令能补救⼀下,**该命令⽤来记录本地的每⼀次命令。这样,你就可以很方便地找到你的所有操作记录了。

但问题来了:d79e182 是啥东西?这个是 add version3 的 commit id(部分)。没错,Git 版本回退的时候,也可以使用部分 commit id 来代表目标版本。

例如,现在你想回到 version3,只需要执行:

cpp 复制代码
git reset --hard d79e182

Git 会自动识别出这是哪个完整的 commit id,并帮你完成回退。


补充说明

你的 commit id 对应版本 说明
d79e182 add version3 最新的一次提交(第三次)
ec57baa add version2 第二次提交(当前 HEAD 指向这里)
87e130d add version1 第一次提交

你当前的 HEAD 指向 ec57baa(version2),但通过 git reflog 可以看到 d79e182(version3)依然存在,只是因为 HEAD 移动了,git log 默认不显示它而已。

说白了就是 git reflog****记录了本地仓库的所有操作历史,是找回"丢失"提交的利器。即使 git log 看不到某个提交,只要 git reflog 里还有记录,就能用 git reset --hard <commit-id> 恢复回来。而且 Git 支持使用 commit id 的前几位作为简写,只要不重复即可。


回退到v3


查看⼯作区


查看log

可往往是理想很丰满,现实很⻣感。在实际开发中,由于⻓时间的开发了,导致 commit id 早就找不到了,可突然某⼀天,我⼜想回退到 version3,那该如何操作呢?貌似现在不可能了。


值得说的是,Git 的版本回退速度⾮常快,因为 Git 在内部有个指向当前分⽀(此处是master)的
HEAD 指针, refs/heads/master ⽂件⾥保存当前 master 分⽀的最新 commit id 。当我们
在回退版本的时候,Git 仅仅是给 refs/heads/master 中存储⼀个特定的version,可以简单理解
成如下⽰意图:


1.7撤销修改

如果我们在我们的⼯作区写了很⻓时间代码,越写越写不下去,觉得⾃⼰写的实在是垃圾,想恢复到上⼀个版本

1.情况⼀:对于⼯作区的代码,还没有 add

向ReadMe中新增⼀⾏代码


直接删除代码


⾟亏我们⼯作效率不⾼,才写了⼀⾏代码就发现不⾏了,要是你写了3天,⼀直都没有提交,该怎么删掉呢?你⾃⼰都忘了⾃⼰新增过哪些,有同学说,我可以 git diff xxx ⼀下,看看差别在删啊,

那你肯定⼜要花3天时间删代码了,并且很⼤的概率还会改出bug。⼀周过去了,你怎么向你的⽼板交代呢?

Git 其实还为我们提供了更好的⽅式,我们可以使⽤ git checkout -- file 命令让⼯作区的
⽂件回到最近⼀次 add 或 commit 时的状态。 要注意 git checkout -- file 命令中的
-- 很重要,切记不要省略,⼀旦省略,该命令就变为其他意思了,后⾯我们再说。⽰例如下:

向ReadMe中新增⼀⾏代码


恢复到上⼀次 add 或 commit


2.情况⼆:已经 add ,但没有 commit

add 后还是保存到了暂存区呢?怎么撤销呢?

向ReadMe中新增⼀⾏代码


add 存⼊暂存区


回忆⼀下学过的 git reset 回退命令,该命令如果使⽤ --mixed 参数,可以将暂存区

的内容退回为指定的版本内容,但⼯作区⽂件保持不变。那我们就可以回退下暂存区的内容了。⽰例如下:

--mixedgit reset 的默认参数,使用时可以省略。该参数的作用是:将暂存区的内容回退到指定版本,但工作区的内容保持不变。

例如,执行以下命令可以将 ReadMe 文件从暂存区移除(即撤销 git add):

cpp 复制代码
git reset HEAD -- ReadMe

注意--mixed 是默认选项,所以可以不写。-- 用于明确区分参数和文件路径,防止因文件名与分支名重名而产生歧义。

执行后,Git 会提示

其中 M 表示 ReadMe 文件已被修改(Modified),但此时该修改仅存在于工作区,暂存区中已经没有该文件的记录了。


⽤ git status 查看⼀下,发现现在暂存区是⼲净的,⼯作区有修改。


还记得如何丢弃工作区中未暂存的修改,将文件恢复到上一次提交的状态?

执行命令

cpp 复制代码
git checkout -- ReadMe

作用 :丢弃工作区中 ReadMe 文件的所有修改,将其恢复到暂存区或版本库中的状态。


命令格式说明

cpp 复制代码
git checkout -- <文件名>
  • --:分隔符,用于明确区分分支名和文件名(防止文件名与分支名重名时产生歧义)

  • <文件名>:要恢复的文件

总结:git checkout -- ReadMe 的作用是丢弃工作区中指定文件的修改,将其恢复到最近一次 git addgit commit 时的状态。


3.情况三:已经 add ,并且也 commit 了

不要担⼼,我们可以 git reset --hard HEAD^ 回退到上⼀个版本!不过,这是有条件的,就是

你还没有把⾃⼰的本地版本库推送到远程。还记得Git是分布式版本控制系统吗?我们后⾯会讲到远程版本库,⼀旦你推送到远程版本库,就真的后果有点严重。

向ReadMe中新增⼀⾏代码

提交

回退


1.8删除⽂件

在 Git 中,删除也是⼀个修改操作,我们实战⼀下, 如果要删除 file5 ⽂件,怎么搞呢?如果你这样

做了

但这样直接删除是没有⽤的,反⽽徒增烦恼, git status 命令会⽴刻告诉你哪些⽂件被删除了:

但会发现我这个没显示:原因如下:

因为你删的 file5 从来没有被 Git 追踪过(你之前只 git addgit commitfile1file2file3file4ReadMefile5 只是创建了,从未 add 过)。

Git 的 status 只关心它认识的、曾经管理过的文件。对它来说,file5 是个"局外人",进进出出 Git 根本不管。


对比file1


此时,⼯作区和版本库就不⼀致了,要删⽂件,⽬前除了要删⼯作区的⽂件,还要清除版本库的⽂
件。

⼀般⾛到这⾥,有两种可能:

  • 确实要从版本库中删除该⽂件
  • 不⼩⼼删错了

对第⼆种情况,很明显误删,需要使⽤ git 来进⾏恢复,很简单,我们刚学过(删除也是修改):


有人会说那file5呢?我的答案是它无法恢复在这里看

为什么 git checkout -- file5 会报错?

在上面的操作中,我们执行 git checkout -- file5 时,遇到了这样一个错误:

cpp 复制代码
error: pathspec 'file5' did not match any file(s) known to git

翻译过来就是:file5 这个文件不在 Git 的"花名册"里,Git 根本不认识它,所以没法帮你恢复。

根本原因在于:git checkout -- 文件名 只能恢复被 Git 追踪过的文件 。所谓的"被追踪",是指该文件曾经被 git add 过并提交到了版本库中,Git 的版本库里存有它的历史记录,因此才能将其恢复。

回顾我们的操作:

cpp 复制代码
rm file5          file5 从未被 git add 过,是未被追踪的文件
git checkout -- file5    报错!Git 不认识它

file1 就不一样了:

cpp 复制代码
rm file1          file1 之前被 git add 和 git commit 过,是被追踪的文件
git checkout -- file1   成功恢复!

因为 file1 之前被 git add 并提交过,Git 的版本库里存有它的副本,所以能顺利恢复。

结论git checkout -- 文件名 只能恢复那些曾经被 Git 追踪过的文件。对于从未被 git add 过的文件,删除后就真的消失了------Git 也无能为力。所以,当你创建一个新文件并打算长期保留时,记得及时 git addgit commit,让它进入 Git 的"保护名单"。

一句话说明白:没有被 Git 追踪过的文件,删了就真的没了,Git 也没办法。


对于第⼀种情况,很明显是没有删完,我们只删除了⼯作区的⽂件。这时就需要使⽤ git rm 将⽂
件从暂存区和⼯作区中删除,并且 commit :


rm vs git rm 对比

对比维度 rm file git rm file
工作区 删除 删除
暂存区 保留(索引中仍有该文件记录) 删除(索引记录同步移除)
是否自动暂存删除操作 否,需要手动 git addgit commit -a 是,删除操作已自动进入暂存区
后续操作 需要额外执行 git addgit commit -a 直接执行 git commit 即可完成删除
Git 状态提示 Changes not staged for commit: deleted: file Changes to be committed: deleted: file

一句话讲明白

rm file 只删工作区,暂存区里还有残留记录,需要再处理一次才能提交删除;git rm file 一次性把工作区和暂存区都删干净,直接 commit 就能完成删除操作。简单说:rm 只管删文件,git rm 既删文件又告诉 Git "我要删它了"。


1.9Git reset 三种模式

先回顾一下 Git 的三个区域:

  • 工作区:就是你电脑上打开文件夹看到的那些文件,能直接改

  • 暂存区git add 之后文件放的地方,相当于"候车室"

  • 版本库git commit 之后存下来的版本,每一次提交就是一张"照片"

开始我们统一在 ReadMe 里写 git world,三个区域都一样。然后执行回退,把版本库从 git world 退回到 git,看三个区域分别怎么变。


2.三种模式

1. git reset --soft HEAD~1
cpp 复制代码
git reset --soft HEAD~1

变完啥样,用这个表格告诉你:

区域 变成啥了
版本库 git
暂存区 还是 git world
工作区 还是 git world

用人话说: 就动了指针,其他啥也没碰。原来在暂存区的东西还在,不用重新 git add,直接 git commit 就能再来一次。

啥时候用: 我自己的理解是------比如写了一上午代码,零零碎碎 commit 了四五次,想把这些合成一个有意义的提交,就用这个。课上老师举的例子是"合并提交记录"。


2. git reset --mixed HEAD~1(默认)

不写参数的时候默认就是这个,git reset HEAD~1 就是它。

cpp 复制代码
git reset HEAD~1

变完啥样,表格告诉你:

区域 变成啥了
版本库 git
暂存区 git
工作区 还是 git world

用人话说: 指针移了,暂存区也清了,但工作区的修改还在。相当于 git add 被撤回来了,文件变成"改了但还没 add"的状态。

啥时候用: 这个我踩过坑。有一次我 git add . 把所有文件都扔进去了,后来发现有个文件不应该提交,就用这个把暂存区清空,重新挑着 add。老师讲的时候说这叫"撤销暂存"。

注意**:** 工作区的修改不会丢,放心用。


3. git reset --hard HEAD~1
cpp 复制代码
git reset --hard HEAD~1

变完啥样,表格如下:

区域 变成啥了
版本库 git
暂存区 git
工作区 git

用人话说: 三个区域全部同步回退。工作区里还没提交的修改,直接没掉。

这个有一个坑,后面细说。

啥时候用: 老师说一般是"彻底放弃所有改动,强制回到某个历史版本"的时候用。我自己是觉得,除非你确定当前改的东西完全不要了,否则别碰它。

3.三个模式对比

参数 版本库 暂存区 工作区 一句话人话
--soft 回退 不变 不变 只挪指针,啥也不动
--mixed 回退 回退 不变 撤销 add,修改还在
--hard 回退 回退 回退 全部回去,没提交的没了

4.注意要点

先说 --hard 的教训。

有一次我改了代码,没 commit,但是我执行了 git reset --hard,改的东西全没了。还好我可以用 git reflog 找回。后面就如果,非要用 --hard 的话,先执行 git stash

bash 复制代码
git stash            把现在的修改临时存起来
git reset --hard HEAD~1
git stash pop        需要的时候再恢复

虽然多敲两行,但保,再说一个容易忽略的点:

git reset 只影响本地仓库。如果代码已经 push 到远程了,本地 reset 完再 push 会报错,得用 git push -f。这个 -f 在小组项目里要小心,会把别人的提交覆盖掉。

5.简单小结

自己整理了一遍之后,总结了三句话:

  • 想合并提交 --> --soft

  • 想撤销 git add --> --mixed(默认)

  • --hard 少用,非要用先 stash


思考题:万一不小心 git reset --hard 了,除了 git reflog 还有别的办法吗?
有,但有限。

git reflog 是最常用的恢复方式,如果它也不行,还可以试试 git fsck --lost-found,这个命令会找出所有"丢失"的提交对象,不过找出来的东西比较乱,需要自己一个一个翻。

另外,如果你用的是 VS Code、IDEA 这类编辑器,可以看看编辑器的本地历史记录,有时候能找回单个文件的内容。再不行就只能靠操作系统的回收站或者数据恢复工具了,但那个成功率就看运气了。

不过说实话,与其想着怎么恢复,不如养成好习惯:执行 git reset --hard 之前,先 git stash 把当前修改存起来,或者干脆手动复制一份备份。再好的恢复手段,也不如提前做好预防。

相关推荐
zzzll11111 小时前
n8n 工作流自动化平台入门指南
运维·自动化
多加点辣也没关系1 小时前
Git - 的安装与使用
大数据·git
帅次1 小时前
Git 常用命令汇总
git·软件工程·开发工具·版本控制·git教程·代码管理·git命令
Hrain-AI1 小时前
Anthropic oncall-kit 开源拆解:运维 Agent 落地范式的四基石与权限边界
运维·人工智能·开源
陈皮波比茶1 小时前
git学习
git·学习
HXDGCL1 小时前
从“转盘困局”到“直线破局”:华创力科技PTS精密分度输送系统如何重塑自动化产线
运维·科技·自动化
戴西软件2 小时前
国内有哪些智能化RPA工具?——从“录数据”到“做判断”,国产数字员工正在重新定义自动化
运维·自动化·rpa
Linux-lucky2 小时前
28-Linux学习之旅之Maven与Nexus制品库
linux·运维·学习·nginx·tomcat·maven
Zhu7584 小时前
在Docker环境离线部署最新版Harbor
运维·docker·容器