目录

Git 是什么

存在问题

版本控制器

Git 介绍

安装 Git

Git 基本操作

创建本地仓库

配置 Git

工作区、暂存区、版本库

添加文件

修改文件

版本回退

撤销修改

第一种:工作区中修改未 add

第二种:工作区中修改已 add,但未 commit

第三种:工作区修改已 add,且已 commit

删除文件


Git 是什么

存在问题

我们来看一个场景:在编写文档时,文档可能会经历以下变化:

若我们直接在一个文档上进行修改,此时,我们需要 v2 版本,就没办法从当前版本回退到 v2 版本了

因此,为了防止文档丢失,更改后能够恢复到原来版本,我们需要每一次更改完成后复制出一个副本

随着版本迭代,产出的文件也就越来越多,并且,随着版本数量的增多,我们很难记清楚每个版本都修改了哪些内容

因此,上述存在的问题为:需要记录文件每个版本的内容变化,并且能够回退到任意历史版本

我们可以使用 版本控制器 来解决上述问题

版本控制器

版本控制器(Version Control System,简称 VCS)​ 是用来跟踪和管理文件内容变化的系统​。它记录文件从创建到每次修改的完整历史,允许你随时回溯到任意历史版本,就像给项目安装了一台“时光机”。

Git 则是目前主流的版本控制器

Git 介绍

Git 是一个分布式版本控制系统,核心任务是跟踪和管理文件的变化,让我们可以记录项目的完整历史,并支持多人协作开发

任何文件(文本文件、二进制文件、图像、视频等)都可以交给 Git 管理,Git 会记录每次提交时的变化

虽然 Git 可以管理任何文件,但它在处理 文本文件 和 二进制文件 时有所不同:

文本文件:由于文本文件是逐行存储的,因此 Git 可以轻松比较每个版本的差异,并且记录变化内容,例如:记录当前文档第五行新增单词"word",第三行删除单词"hello",代码文件(如 .java, .py, .js, .html, .css 等)、配置文件(如 .json, .xml, .yaml, .properties 等)、文档(如 .md, .txt 等)都是文本文件,非常适合用 Git 管理

二进制文件:二进制文件(如图片 .png, .jpg,可执行文件 .exe,压缩包 .zip,文档 .docx, .pdf 等)在 Git 中也可以被管理,但 Git 无法直接比较两个二进制文件的具体差异,因此,每次都只能记录当前文件完整副本无法知道每次变化的具体修改内容

接下来,我们就来安装 Git,并进一步学习该如何使用 Git

安装 Git

Git 可以在几乎所有主流操作系统上安装,在这里,我们在  Linux-ubuntu 上进行安装

使用  sudo apt install git  命令就可直接安装 Git,安装后,使用 git --version 查看 Git 安装版本

此时,Git 就安装成功了,接下来,我们就可以使用 Git 了

Git 基本操作

创建本地仓库

我们要想使用 Git 对文件进行追踪管理,需要先创建出一个 Git 仓库

先创建一个文件目录(git),再在该文件目录下执行 git init 命令,从而创建出 Git 本地仓库:

可以看到,执行 git init 命令初始化后,当前目录下多了一个 .git 隐藏目录,我们查看 .git 目录下内容:

而这个 .git 隐藏目录,正是 Git 仓库的核心,由它来完成文件版本的追踪和管理,对于其中的具体结构和内容,我们后续慢慢学习

注意不要手动修改 .git 目录下的文件,一旦改乱了,就会破坏 Git 仓库

配置 Git

安装完 Git 后,需要设置 用户名称 email 地址

git config [--global] user.name "userName" 
git config [--global] user.email "email@example.com"

其中,--global 是一个可选项,若使用该选项,表示这台机器上所有 Git 仓库都会使用这个配置。若不同仓库需要配置不同的 name email,可以不使用 --global 选项,需要注意:命令需在仓库中执行

配置完成后,我们可以使用 git config -l 查看当前配置:

若我们需要删除配置 用户名称 email 地址,可使用对应删除命令:

git config [--global] --unset user.name
git config [--global] --unset user.email

工作区、暂存区、版本库

Git 的工作区、暂存区、版本库是 Git 的三个重要概念,能够帮助我们理解文件在 Git 中的不同状态和流转过程,接下来,我们就来学习一下这三个概念以及它们之间的关系:

工作区(working directory)项目文件目录(上述创建的 git 目录就是),可以直接修改、添加、删除文件

暂存区(stage / index):中间区域,用于准备下次提交内容,一般存放在 .git 目录下的 Index 文件中,因此也将暂存区称为索引

版本库(repository):工作区中的隐藏目录 .git 不属于工作区,而是 Git 的版本库,其中的所有文件都可以被 Git 管理起来,每个文件的修改、删除和修改都可以被 Git 跟踪,后续任何时刻都可以追踪历史,也可以在某个时刻进行还原

我们通过下图来看工作区、暂存区和版本库之间的关系

图中左侧为工作区,右侧为版本库暂存区 位于版本库中

在我们创建 Git 版本库时(执行 git init 命令),Git 会自动为我们创建一个唯一的 master 分支,以及指向 master 的指针 HEAD

当我们对工作区进行修改(新增文件、修改文件或删除文件),再执行 git add 命令,暂存区目录树的文件索引就会被更新:

而在执行了 git add 命令后,再执行提交操作 git commit 命令,master 分支就会进行对应更新:

通过上述流程,我们可以发现:当我们在项目目录下新增文件,此时并不能称之为向仓库中新增文件,只是在工作区新增了文件,需要使用 git add git commit 命令才能将文件添加到仓库中进行管理

接下来,我们就来创建一个新文件,并将其添加到仓库中进行管理

添加文件

我们先在目录下创建一个 test 文件,并使用 git add 命令将文件添加到暂存区:

# 添加一个或多个文件到暂存区
git add [file1] [file2] ...
# 添加指定目录到暂存区,包括子目录
git add [dir]
# 添加当前目录下的所有文件改动到暂存区
git add .

创建文件并添加到暂存区:

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

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

其中,message 表示本次提交的描述,由我们自己指定 -m 选项和 message 内容都不能省略,message 需要描述出此次提交的细节,方便我们后续查看

git commit 命令执行成功后,提示我们 1 个文件被改动(也就是新增了 test 文件), 0 行插入,0 行删除

我们对 test 进行编辑,在其中添加内容,再次 add 和 commit:

提交之后,提示我们 1 个文件被改动,插入了 2 行内容,未进行删除

此时,我们可以使用 git log 命令来查看我们的历史提交记录:

其中 75830f46a9edc85d206f2d7e44e81e9da190c54b 和 c4fd7ace30b22f0257fabce2bf2625431359b670 是每次提交的 commit id(版本号),由SHA-1算法生成,共40位十六进制数

通过 git log,我们就可以看到每次提交的 commit id、作者信息、提交时间以及 提交的message描述

此时我们再来查看 .git 目录:

对比之前的 .git 目录:

可以看到了新增了 index 目录,也就是暂存区,git add 后会更新该目录

objects 为 Git 的对象库,它存储了所有的Git对象,包括 Blob(文件内容)Tree(目录结构)Commit(提交信息)Tage(标签信息)四种类型对象,这些对象以 哈希值 命名,其中,对象的前2位作为目录名后38位作为文件名

我们之前看到的历史提交都在其中:

由于文件经过 SHA(安全哈希算法)加密过,我们无法使用 cat 命令查看,但可以使用 Git 提供的 git cat-file 命令来查看版本库对象的内容

我们查看最近一次提交的 commit 对象

其中:

tree:指向一个 tree 对象,表示该提交对应的目录结构

parent:父提交,说明本次提交不是首次提交,而是基于前一个提交的修改,也正是 git log 中的上一次提交的 commit id

我们继续查看 tree 对象

再继续查看存储 test 文件的 blob 对象

其中存储的就是我们对 test 文件进行的修改

我们再来看 HEAD 指针

它指向了 master 分支,也就是 .git/refs/heads/master,我们继续查看 .git/refs/heads/master:

.git/refs/heads/master 中存储的正是当前最新的 commit id

修改文件

Git 跟踪和管理的是修改,而不是文件

在文件中新增了 n 行、删除了 n 行、创建了新文件和删除旧文件,都称之为修改

我们对 test 文件进行一次修改:

此时,仓库中的 test 文件与工作区的 test 文件不同,可以通过 git status 命令查看当前仓库状态:

我们可以看到,test 文件已被修改,但还未进行提交和修改

那么,我们如何知道我们修改了哪些内容呢?

可以通过 git diff 命令来查看 暂存区 工作区 文件的差异,其显示的格式是 Unix 通用的 diff 格式:

其中:

diff --git a/test b/test:表示比较的是  test 文件的两个版本(工作区和暂存区)

index 723e4ec..b66f020:表示索引变化,索引从 723e4ec(原版本)变为 b66f020(新版本)

100644:表示 test 文件是普通文件

--- a/test:表示原文件

+++ b/test:表示新文件

@@ -1,2 +1,3 @@:原文件的第 1 行到第 2 行,新文件的第 1 行到第 3 行

 hello:第 1 行内容(未改变)
 hhh:第 2 行内容(未改变)

+111:第 3 行内容(新版本新添加)

也就是说,我们对 test 文件进行了修改,在其中第三行添加了内容 "111"

知道了 test 文件有哪些修改后,我们再将其添加到本地仓库,此时再查看仓库状态:

此时提示我们有新的修改需要进行 commit,我们此时再使用 git commit 命令提交修改:

此时,再查看仓库状态,显示没有修改需要提交,工作区也非常干净

版本回退

我们知道,Git 能够管理文件的历史版本,这也是版本控制器的重要能力之一

如果当前我们的修改出现了问题,需要回退到指定历史版重新开始

这时候,该如何进行版本回退呢?

通过 git reset 命令,就可以进行版本回退,而回退的本质,是将版本库中的内容进行回退,至于 工作区暂存区 是否需要进行回退,由 git reset 命令的参考进行决定

git reset 命令语法格式: git reset [--soft | --mixed | --hard] [HEAD]

--soft工作区和暂存区内容都不变,只是将版本库回退到某个指定版本

--mixed默认选项,使用时可不用带该参数,该参数将暂存区内容回退为指定提交版本内容,工作区文件保持不变

--hard:将暂存区和工作区都回退到指定版本,工作区中有未提交的代码时,回滚后,未提交的代码也就再也找不回了,因此该参数使用前需慎重考虑

HEAD:当前版本,也可以是 commit id(指定回退的版本),而 HEAD^表示上一个版本,同理,HEAD^^ 表示上上一个版本...;也可以使用 ~数字表示回退版本,如 HEAD~0 表示当前版本,HEAD~1 表示上一个版本,HEAD~2表示上上一个版本...

我们通过一个表格来看 --soft 、 --mixed 、--hard 的区别

工作区暂存区版本库
--soft不回退不回退回退
--mixed不回退回退回退
--hard回退回退回退

我们先对 test 文件进行三次修改并提交:

我们使用 git log 命令来查看提交记录,可以使用 --pretty 参数来指定输出格式, --pretty=oneline 表示单行格式,一行为一个提交,前面是 commit id,后面是提交信息的第一行

我们在提交完 version3 后,发现 version3 存在问题,想要回退到 version2,基于 version2 重新修改,在这里,我们想要将工作区的内容也回退到 version2 版本,因此,需要使用 --hard 参数:

我们看到,此时 HEAD 指针指向 version2,我们再查看 test 文件内容:

对应的正是 version2 版本内容

但是,若此时我们又想回到 version3,该如何处理?

我们仍然可以使用 git reset 命令,从而回退到version3,但是,我们需要知道  version3 的 commit id

我们可以通过 git reflog 命令来查看,git reflog 命令记录了本地的每一次命令

这样,我们就可以找到 version3 的 commit id,但这里的 commit id 是完整 commit id 的一部分,但在版本回退时,可以使用部分 commit id 代替目标版本 commit id

可以看到,此时又回退到 version3

Git 在进行版本回退时,其回退速度非常快,这是为什么呢?

因为 Git 内部有一个指向当前分支的 HEAD 指针,当前 HEAD 指针指向 master 分支,也就是 refs/heads/master,而 refs/heads/master 中保存了当前分支的最新 commit id

而在进行版本回退时,Git 仅仅是修改了 refs/heads/master 中存储的 commit id

撤销修改

若我们当前在工作区中进行修改,但此时想撤销当前修改,回到上一版本

此时,该如何操作呢?

我们需要对工作区中的修改状态分情况讨论

第一种:工作区中修改未 add

我们在工作区进行了修改,但还未执行 git add:

对于上述未 add 的修改,我们可以使用 git checkout -- [file] 命令,让工作区的文件回到最近一次 add 或 commit 时的状态,注意,其中的 -- 非常重要,不能省略

此时工作区中修改已被撤销

第二种:工作区中修改已 add,但未 commit

若我们此时已经将工作区中的修改进行了 add 操作,将其保存到暂存区,此时该如何撤销呢?

我们可以使用 git reset 命令来回退到当前版本

若使用 --mixed 参数,则可将暂存区中内容回退到指定版本,,但工作区修改保持不变,此时,就回到了第一种情况,我们可以继续使用 git checkout -- [file] 命令来撤销工作区中修改

我们也可以直接使用 --hard 参数,将暂存区和工作区的修改都回退到当前版本

第三种:工作区修改已 add,且已 commit

对于这种情况,我们可以使用 git reset --hard HEAD^,将工作区和暂存区中修改都回退到上一版本:

删除文件

在 Git 中,删除也是一个修改操作,若我们想要删除 Git 中的文件,该如何操作呢?

在进行文件删除时,不仅要删除工作区中文件,还需要清除版本库和暂存区中文件,因此,我们可以使用 Git 提供的 git rm [file] 命令来进行删除,并 commit 提交删除修改:

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐