GIT 基本使用及其原理

2,761 阅读17分钟

前言

  Git是一个开源的分布式版本控制系统,用于敏捷高效地处理任何或大或小的项目,它实际上是一个Git key-value⽂件存储系统。

学习重点

Git的基本使用

Git操作名词介绍

Git常⻅⼯作流程详解

git reset与git checkout命令讲解

Git key-value⽂件系统

⼿动构建git commit tree

1. Git的基本使用

1.1 git初始化

  使用Git前,需要先建立一个仓库(repository)。你可以使用一个已经存在的目录作为Git仓库或创建一个空目录,使用当前目录作为Git仓库,可以使用如下命令:

git init

  使用指定目录作为Git仓库,命令如下所示:

git init path

1.2 Git局部用户信息操作

1.2.1 查看Git局部用户信息

//查看git用户名
git config user.name
//查看git用户邮箱
git config user.email

1.2.1 修改Git局部用户信息

//修改git用户名
git config user.name 新用户名
//修改git邮箱 
git config user.email 新邮箱

1.3 Git全局用户信息操作

1.3.1 查看Git全局用户信息

//查看全局用户名
git config --global user.name
//查看全局用户邮箱
git config --global user.email

1.3.2 修改Git全局用户信息

//修改全局用户名
git config --global user.name 新的用户名
//修改全局用户邮箱
git config --global user.email 新的邮箱

1.4 添加文件到git仓库

  可以使用add命令添加文件到索引/暂缓区,在添加此文件到索引/暂缓区之前,会先判断索引/暂缓区文件index是否已包含此文件的关键信息,如果已有此文件的关键信息,就不会做任何操作(即使git文件系统并不存在此文件的二进制文件,也不会添加到git文件系统),如果index文件不包含此文件的关键信息,则会先将此文件的二进制数据添加到git文件系统(也就是objects文件夹,如果objects中不存在此文件的二进制数据文件),命令使用如下所示:

  git add filename

  在使用这个命令之前,先来看看git中存储文件数据,如下图所示:

image.png

  可以发现,在git初始化之后,尚未添加任何文件之前,索引/暂存区是没有数据的,git文件系统中也并没有添加任何文件。

  使用上面的命令添加文件,如下图所示:

image.png

  可以发现的是,这个命令会将文件添加到索引/暂缓区(也就是新增加的index文件,这个文件并不存储文件内容,只是存储文件的一些关键信息),并且将这个文件添加到git文件系统中,可以使用如下命令查看index文件中的16进制数据:

   //查看git index文件中的16进制数据
   hexdump index文件路径

   //计算获取文件的hash值
   git hash-object 文件路径

  如下所示,其中红框部分数据就是添加到索引/暂缓区的文件的hash值:

image.png

image.png

1.5 从git仓库删除文件

  可以使用rm命令删除文件,如下所示:

   git rm filename

  具体使用如下图所示:

image.png

  单独使用rm会报错,根据注释可知,使用rm命令需要传入参数,如下所示:

  • --cached:这个参数代表仅从索引/暂缓区删除文件,但是不从工作目录中删除文件,完整命令如下所示:
git rm --cached 文件路径

  上面的命令仅会将暂缓区中的文件删除,工作目录中的文件还是在的,如下图所示:

image.png

  查看index文件中的16进制数据,可以发现索引/暂缓区会将这个文件的信息移除,如下图所示:

image.png

  • -f:强制删除文件(索引/暂缓区以及工作目录中的此文件都会被删除)
git rm -f 文件路径

  上面的命令仅会将暂缓区中的文件以及工作目录中的文件都删除,如下图所示:

image.png

  查看此时索引/暂缓区文件index中的16进制数据,如下图所示:

image.png

注意:删除操作并不会清除掉被删除文件在git文件系统中对应的二进制文件

1.6 git add .命令

  这个命令的作用是将.git文件所在目录中所有未添加到索引/暂缓区文件index中的文件(包括修改了的文件)的关键信息添加到index文件,在添加某个文件的关键信息到index文件中前,需要先判断这个文件是否已被添加到git文件系统(也就是objects文件夹中),如果没有,则先添加到git文件系统。

   //其中.代表的是.git文件所在目录
   git add .

  在使用git add .命令之前,我们先来创建两个txt文件,并记录一下当前git的状态,如下图所示:

image.png

  首先来看看index文件中16进制信息,如下图所示:

image.png

  再来看看git文件系统中存储的内容,如下图所示:

image.png

  执行git add .命令,如下图所示:

image.png

  首先查看所有txt文件的hash值,如下图所示:

image.png

image.png

  再来看看git文件系统中增加了什么内容,如下图所示:

image.png

  最后再来看看索引/暂缓区文件index中新增加了什么内容,如下图所示:

image.png

  修改file_1.txt文件中的内容,如下图所示:

image.png

  修改后的file_1.txt文件的hash值如下图所示:

image.png

  再次执行git add .命令,如下图所示: image.png

  再来看看git文件系统中增加了什么内容,如下图所示:

image.png

  最后再来看看索引/暂缓区文件index中新增加了什么内容,如下图所示:

image.png

  可以发现的是索引/缓存区文件index中存储的是最后一次执行git add .命令后文件的关键信息(如果多次修改了同一个文件并执行了git add .命令,那么会修改index文件中原先文件对应位置的文件信息),但是git文件系统中会保存每一次执行git add .操作中产生的新的文件的二进制数据。

1.7 git commit命令

  这个命令主要是用来将索引\暂缓区文件index中所记录的文件信息提交到本地代码仓库:

  在之前操作的基础上,再来执行git commit命令,这个命令用法如下所示:

   git commit -m '这里填写一些提交信息'
   //也可以使用如下命令,这个命令会先执行git add .,然后在执行git commit操作
   git commit -am '这里填写一些提交信息'

  在执行这个命令之前我们先来记录原先git的状态信息,首先是index文件中的内容,如下所示:

image.png

  再记录一下git文件系统中的内容,如下图所示:

image.png

  执行git commit命令,如下图所示:

image.png

  现在来看看git文件系统中增加了什么内容,如下图所示:

image.png

  最后再来看看索引/暂缓区文件index中新增加了什么内容,如下图所示:

image.png

  然后再来修改一下file_3.txt的内容添加到索引区,看看数据会有怎样的变化,如下图所示:

image.png

  执行git add .命令并查看修改后file_3.txt文件的hash值,如下图所示:

image.png

image.png

  查看git文件系统中增加的内容,如下图所示:

image.png

  再来看看索引/暂缓区文件index中新增加的内容,如下图所示:

image.png

  再次使用git commit命令提交代码,如下图所示:

image.png

  查看git文件系统中增加的内容,如下图所示:

image.png

  再来看看索引/暂缓区文件index中新增加的内容,如下图所示:

image.png

1.8 查看当前分支git commit历史记录

  如果你想查看当前分支git commit历史记录,可以使用下面的命令:

git log --oneline --decorate --graph --stat

  使用效果如下图所示:

image.png

image.png

1.9 回滚提交的代码

  回滚提交的代码使用的是git reset命令(也可以指定回滚到某一次提交的版本),其中HEAD是用来指向分支的最后一次提交对象,如下所示:

1.9.1 仅回滚当前HEAD

  使用到的完整的命令如下所示:

   //回滚HEAD,建议使用这个,HEAD~后面的序号代表回退到倒数第几次提交
   git reset --soft HEAD~1

  使用举例,如下图所示:

image.png

  查看回滚之后的提交记录

image.png

  通过这种回滚方式,最后一次commit记录就不存在了,但是index文件中的内容是没有变化的,如下图所示:

image.png

  执行git status命令查看git中文件状态,会提示你修改了file_3.txt文件,此文件信息已添加到index文件中但并未提交到本地代码仓库,并且工作目录中此文件还是存在的,如下图所示:

image.png

  再次提交代码,如下图所示:

image.png

image.png

  在以上基础上回滚index,使用命令如下所示:

git reset HEAD .
//或者
git reset --mixed HEAD .

  执行命令,如下图所示:

image.png

注意:图片中信息有错,git reset HEAD .回滚的是最后一次提交之后对index文件所有的操作

  在以上基础上回滚工作目录,使用命令如下所示:

git checkout -- .

  执行命令,如下图所示:

image.png

1.9.2 回滚当前HEAD以及index(索引/暂缓区)

  完整命令如下所示:

git reset HEAD~1
//或者
git reset --mixed HEAD~1

  使用举例,如下图所示:

image.png

  通过这种回滚方式,最后一次commit记录就不存在了,并且index文件中的内容是也改变了,如下图所示:

image.png

image.png

  执行git status命令查看git中文件状态,会提示你修改了file_3.txt文件,此文件信息还未添加到index文件中,也未提交到本地代码仓库,并且工作目录中此文件还是存在的,如下图所示:

image.png

image.png

  执行git add .命令并提交代码,如下图所示:

image.png

1.9.3 回滚当前HEADindex(索引/暂缓区)以及工作目录

  这种操作是不可逆的,所以慎用!!!,因为会将文件信息从工作目录删除!!!,器完整命令如下所示:

   git reset --hard HEAD~1

  在执行此操作之前,先来看看gitcommit记录,如下图所示:

image.png

  查看git文件系统中增加的内容,如下图所示:

image.png

  再来看看index文件中的数据,如下图所示:

image.png

  file_3.txt文件内容如下图所示:

image.png

  执行git reset --hard HEAD~1命令,如下图所示:

image.png

  查看提交记录,最后一次commit记录就被删除了,如下图所示:

image.png

  执行回滚操作后,git文件系统中存储的数据并没有发生改变,如下图所示:

image.png

  再来看看index文件中的数据,如下图所示:

image.png

  file_3.txt文件内容如下图所示:

image.png

1.11 branch的使用

  • 使用branch创建分支,命令如下所示:
   git branch 分支名

  使用示例,如下图所示:

image.png

  • 使用branch查看分支信息,命令如下所示:
   git branch
   //或者
   git branch --list
   
   //上面两个命令仅能查看本地分支,如果也想要查看远程分支可以使用下面的命令
   git branch -r
   
   //查看所有分支(本地以及远程)
   git branch -a

  使用示例,如下图所示:

image.png

image.png

  • 使用branch删除分支,命令如下所示:
   git branch -d 分支名

  使用示例,如下图所示:

image.png

1.10 checkout的使用

  • 使用checkout切换分支,命令如下所示:
   //切换分支
   git checkout 分支名

  使用示例,如下图所示:

image.png

image.png

  • 使用checkout创建新分支并立即切换,命令如下所示:
   git checkout -b 新分支名
   //切换到远程仓库分支,使用如下命令
   git checkout -b 远程仓库别名 远程仓库别名/远程仓库分支

  使用示例,如下图所示:

image.png

image.png

  • 使用checkout重新存储工作区文件,命令如下所示:
   //重新存储工作区文件
   git checkout -- .

image.png

2. Git的存储原理

  git实际上是没有文件夹的概念的,是通过保存文件的路径来保存文件的,为了验证这个观点,创建一个文件夹,执行如下图所示的操作:

image.png

image.png

  如果你需要提交一个空的文件夹,可以在这个空文件夹中创建一个隐藏文件,命名为.keep或者.gitkeep,然后就可以提交成功了,操作流程如下图所示:

image.png

2.1 git add .命令的本质

  首先在如下图所示路径中创建如下图所示的文件:

image.png

  初始化git,并查看.git中文件,如下图所示:

image.png

image.png

  执行git add .命令后,在objects文件夹中产生如下图所示文件:

image.png

  实际上以上生成的文件夹名称+文件夹中文件名就是工作目录中的文件的hash值,而这些文件中存储的内容就是工作目录中对应文件的二进制数据,可以使用如下的命令打印文件中的内容:

注意:每当有文件的内容(而不是路径)被修改并执行git add .命令后都会在objects生成新的文件,因为这个文件的hash值已经发生了改变

//-p代表完美打印
git cat-file -p 文件夹名称+文件夹中文件名

  打印结果如下图所示:

image.png

  也就是说,Git将存储对象的40HASH分为两部分:

  • 2位作为文件夹

  • 38位作为对象文件名

  Git如此设计目录结构,而不是直接使用40hash作为文件名的原因主要如下:

  • 部分文件系统对目录下的文件数量有限制。例如,FAT32限制单目录下的最大文件数量是65535个。

  • 部分文件系统查找文件属于线性查找,目录下的文件越多,访问越慢。

  除了生成了objects文件夹中的文件外,git add .操作也生成了一个index文件,该文件表示Git stage的内容。该文件是二进制文件,保存了被stage的文件所有信息,像inode信息、hash值等等,你可以使用如下命令查看index文件中的二进制数据:

hexdump index文件路径

  实际使用如下图所示:

image.png

  如果你将objects文件中生成的对应文件以及index文件删除,得到的就是命令git add .的逆操作,下图就是git add .命令实际所做的事情.

image.png

其实git add .命令是可由两个命令操作代替,如下所示:

  • 计算文件的hash值命令,并写入git文件系统数据库(也就是objects文件夹中),使用命令如下所示:
   git hash-objct -w 文件路径

image.png

  使用举例,如下图所示:

image.png

image.png

image.png

image.png

  你也可以使用如下shell命令将文件写入到git数据库

   for i in {1..3}; \
   do \
   echo "$(git hash-object -w file_${i}.text)";\
   done
  • 将工作目录的文件加入到缓存/暂存区(也就是index文件中),使用如下所示命令:
   // --add 添加指定文件
   // --cacheinfo按照格式添加指定的信息
   // 100644 mode
   git update-index --add --cacheinfo 100644 文件hash值 文件名

  其中mode共有如下图所示几种类型

image.png

  使用示例,如下图所示:

image.png

  • 查看暂缓区存放的文件信息,使用如下的命令
   git ls-files -s

  使用示例,如下图所示:

image.png

2.2 git commit命令的本质

  假设工程文件结构如下图所示,并且所有的文件已使用git add .添加到暂缓区。

image.png

image.png

  索引/缓存区文件index中存储文件如下图所示:

image.png

  git文件系统存储文件如下图所示:

image.png

  实际上git commit命令的本质是由两个命令组成,如下所示:

  • git write-tree:这个命令的作用就是根据当前git index文件中的文件路径生成一些相应的树形结构文件(这些文件中的内容就是其所包含文件的关键信息),并将这些树形结构文件存储在git文件系统中,详情请看下面的操作:
   git write-tree

  执行git write-tree命令后,objects中新生成了四个二进制文件,如下图所示:

image.png

  根据git文件系统中存储文件的hash值查看这个文件的存储内容,可以使用下面的命令:

   //cat-file 查看文件信息
   //-p 更优美的打印文件信息
   git cat-file -p 文件的hash

image.png

  拼接这些文件的hash值,使用命令打印这些文件的内容,如下图所示:

image.png

  以上就是git write-tree命令所做的事情。

  • git commit-tree:将以上所生成的树形结构信息进行提交。

  首先,先来查看一下git提交记录,如下图所示:

image.png

  可以发现的是当前并未提交记录,那么就使用如下命令进行提交:

   git commit-tree tree的hash值 -m "提交信息" -p 父节点的hash值
   //或者使用
   echo "提交信息" | git commit-tree tree的hash值 -p 父节点的hash

  使用如下图所示:

image.png

  查看当前git的状态,如下图所示:

image.png

  并未提交成功,因为git commit-tree命令仅会生成一个commit对象的id,此时还并未将这个commit对象写入到git文件系统,还需要使用下面的命令将这个commit对象写入到某个分支中去:

   git update-ref refs/heads/分支名 commit对象id

  使用如下图所示:

image.png

  以上的命令将会产生如下图所示的数据:

image.png

image.png

image.png

  通过命令查看bb3a7ee7d65f44bf8a5bf3a923dc953d3a525838文件中的内容,如下图所示:

image.png

2.3 ⼿动构建git commit tree

  执行完git write-tree命令后,git自动构建了4个树,其实,也可以使用以下的命令构建一个单独的新树,命令如下图所示:

//构建一个父树,并指定其子树
//read-tree 将给出的树写入索引但不写入缓存
//--prefix 更优美的打印Hash值代表的文件内容
git read-tree --prefix=新树名字/ 子树hash值
//构建当前树(父树)
git write-tree 

  使用举例,如下所示:

  • 构建一棵temp_project新树,使project作为其子树

image.png

  执行git write-tree将这个新树的二进制数据保存到git文件系统,如下图所示:

image.png

image.png

  • 构建一棵temp_files2新树,使files2作为其子树

image.png

image.png

  执行git write-tree将这个新树的二进制数据保存到git文件系统,如下图所示:

image.png

image.png

  其实也可以从git中恢复这些构建的树到工作目录,使用如下的命令:

git checkout -- 构建的树名/

  使用示例,如下所示:

  • 恢复temp_project树

image.png

image.png

  • 恢复temp_files2树

image.png

image.png

  • 再次提交代码

  查看git状态,如下图所示:

image.png

  执行git add .命令 image.png

  再次查看git状态,如下图所示:

image.png

  提交代码,如下图示所示:

image.png

image.png

image.png

3. git工作流程

image.png

image.png