简单地说,Git 究竟是怎样的一个系统呢?请注意接下来的内容非常重要,若你理解了 Git 的思想和基本工作原理,用起来就会知其所以然,游刃有余。在学习 Git 时,请尽量理清你对其它版本管理系统已有的认识,如 CVS、Subversion 或 Perforce, 这样能帮助你使用工具时避免发生混淆。尽管 Git 用起来与其它的版本控制系统非常相似, 但它在对信息的存储和认知方式上却有很大差异,理解这些差异将有助于避免使用中的困惑。
Git 初始化代码仓库
执行完成了 git init 命令,究竟做了什么呢? 执行完成如下命令之后,我们可以得到下图所示的内容,右侧的就是 Git 为我们创建的代码仓库,其中包含了用于版本管理所需要的内容。#左边执行 $mkdirgit-demo $cdgit-demo&&gitinit $rm-rf.git/hooks/*.sample #右边执行 $watch-n1-dfind.
➜tree.git .git ├──HEAD ├──config ├──description ├──hooks ├──info │└──exclude ├──objects │├──info │└──pack └──refs ├──heads └──tags
.git/config- 当前代码仓库本地的配置文件-
本地配置文件(.git/config)和全局配置文件(~/.gitconfig)
-
通过执行如下命令,可以将用户配置记录到本地代码仓库的配置文件中去
-
git config user.name "demo"
-
git config user.email "demo@demo.com"
➜cat.git/config [core] repositoryformatversion=0 filemode=true bare=false logallrefupdates=true ignorecase=true precomposeunicode=true [user] name=demo email=demo@demo.com.git/objects- 当前代码仓库代码的存储位置
-
blob类型
-
commit类型
-
tree类型
#均无内容 ➜ll.git/objects total0 drwxr-xr-x2escapestaff64BNov2320:39info drwxr-xr-x2escapestaff64BNov2320:39pack ➜ll.git/objects/info ➜ll.git/objects/pack
.git/info- 当前仓库的排除等信息➜cat./.git/info/exclude #gitls-files--others--exclude-from=.git/info/exclude #Linesthatstartwith'#'arecomments. #ForaprojectmostlyinC,thefollowingwouldbeagoodsetof #excludepatterns(uncommentthemifyouwanttousethem): #*.[oa] #*~.git/hooks- 当前代码仓库默认钩子脚本
./.git/hooks/commit-msg.sample ./.git/hooks/pre-rebase.sample ./.git/hooks/pre-commit.sample ./.git/hooks/applypatch-msg.sample ./.git/hooks/fsmonitor-watchman.sample ./.git/hooks/pre-receive.sample ./.git/hooks/prepare-commit-msg.sample ./.git/hooks/post-update.sample ./.git/hooks/pre-merge-commit.sample ./.git/hooks/pre-applypatch.sample ./.git/hooks/pre-push.sample ./.git/hooks/update.sample
.git/HEAD- 当前代码仓库的分支指针➜cat.git/HEAD ref:refs/heads/master
.git/refs- 当前代码仓库的头指针#均无内容 ➜ll.git/refs total0 drwxr-xr-x2escapestaff64BNov2320:39heads drwxr-xr-x2escapestaff64BNov2320:39tags ➜ll.git/refs/heads ➜ll.git/refs/tags
.git/description- 当前代码仓库的描述信息➜cat.git/description Unnamedrepository;editthisfile'description'tonametherepository.
add 之后发生了什么
执行完成了 git add 命令,究竟做了什么呢? 执行完成如下命令之后,我们可以得到下图所示的内容,我们发现右侧新增了一个文件,但是 Git 目录里面的内容丝毫没有变化。这是因为,我们现在执行的修改默认是放在工作区的,而工作区里面的修改不归 Git 目录去管理。 而当我们执行 git status 命令的时候,Git 又可以识别出来现在工作区新增了一个文件,这里怎么做到的呢?——详见[理解 blob 对象和 SHA1]部分 而当我们执行 git add 命令让 Git 帮助我们管理文件的时候,发现右侧新增了一个目录和两个文件,分别是 8d 目录、index 和0e41.. 文件。#左边执行 $echo"hellogit">helle.txt $gitstatus $gitaddhello.txt #右边执行 $watch-n1-dfind.
#查看objects的文件类型 $gitcat-file-t8d0e41 blob #查看objects的文件内容 $gitcat-file-p8d0e41 hellogit #查看objects的文件大小 $gitcat-file-s8d0e41 10 #拼装起来 blob10hellogit
现在我们就知道了,执行 git add 命令将文件从工作区添加到暂存区里面,Git 会把帮助我们生成一些 Git 的对象,它存储的是文件的内容和文件类型并不存储文件名称。 为了验证我们上述的说法,我们可以添加同样的内容到另一个文件,然后进行提交,来观察 .git 目录的变化。我们发现,右侧的 objects 目录并没有新增目录和文件。这就可以证明,blob 类型的 object 只存储的是文件的内容,如果两个文件的内容一致的话,则只需要存储一个 object 即可。 话说这里 object 为什么没有存储文件名称呢?这里因为 SHA1 的 Hash 算法计算哈希的时候,本身就不包括文件名称,所以取什么名称都是无所谓的。那问题来了,就是文件名的信息都存储到哪里去了呢?——详见[理解 blob 对象和 SHA1]部分#左边执行 $echo"hellogit">tmp.txt $gitaddtmp.txt #右边执行 $watch-n1-dfind.
理解 blob 对象和 SHA1
了解 Git 的 blob 对象和 SHA1 之前的关系和对应计算! Hash 算法是把任意长度的输入通过散列算法变化成固定长度的输出,根据算法的不同,生成的长度也有所不同。 Hash 算法:-
MD5-128bit- 不安全 - 文件校验
-
SHA1-160bit(40位)- 不安全 -Git存储
-
SHA256-256bit- 安全 -Docker镜像
-
SHA512-512bit- 安全
➜echo"hellogit"|shasum d6a96ae3b442218a91512b9e1c57b9578b487a0b-
这里因为 Git 工具的计算方式,是使用类型 长度 内容的方式进行计算的。这里,我们算了下文件内容只有九位,但是这里是十位,这里因为内容里面有换行符的存在导致的。现在我们就可以使用 git cat-file 命令来拼装 Git 工具存储的完整内容了。➜ls-lhhello.txt -rw-r--r--1escapestaff10BNov2321:12hello.txt ➜echo"blob10hellogit"|shasum 8d0e41234f24b6da002d962a26c2495ea16a425f- #拼装起来 blob10hellogit
➜cat.git/objects/8d/0e41234f24b6da002d962a26c2495ea16a425f xKOR04`HWH,6A% ➜ls-lh.git/objects/8d/0e41234f24b6da002d962a26c2495ea16a425f -r--r--r--1escapestaff26BNov2321:36.git/objects/8d/0e41234f24b6da002d962a26c2495ea16a425f ➜file.git/objects/8d/0e41234f24b6da002d962a26c2495ea16a425f .git/objects/8d/0e41234f24b6da002d962a26c2495ea16a425f:VAXCOFFexecutablenotstripped-version16694
其实,我们这里也是可以通过 Python 代码来获取二进制 object 对象的内容的。importzlib contents=open('0e41234f24b6da002d962a26c2495ea16a425f','rb').read() zlib.decompress(contents)
聊聊工作区和暂存区
聊聊工作区和暂存区,以及文件如何在工作区和缓存区之间同步的问题。 之前的章节我们也聊到了,当我们执行 git status 命令的时候,Git 工具怎么知道我们有一个文件没有追踪,以及文件名的信息都存储到哪里去了? 这一切的答案,都要从工作区和索引区讲起。Git 根据其存储的状态不同,将对应状态的“空间”分为工作区、暂存区(也可称为索引区)和版本区三类。具体示例,可以参考下图。
#左边执行 $echo"file1">file1.txt $gitaddfile1.txt $cat.git/index $gitls-files#列出当前暂存区的文件列表信息 $gitls-files-s#列出当前暂存区文件的详细信息 #右边执行 $watch-n1-dtree.git
#左边执行 $gitstatus $echo"file2">file2.txt $gitls-files-s $gitstatus $gitaddfile2.txt $gitls-files-s $gitstatus #右边执行 $watch-n1-dtree.git
#左边执行 $gitls-files-s $echo"file.txt">file1.txt $gitstatus #右边执行 $watch-n1-dtree.git
#左边执行 $gitls-files-s $gitaddfile1.txt $gitls-files-s #右边执行 $watch-n1-dtree.git
理解 commit 提交原理
执行完成了 git commit 命令,究竟做了什么呢? Git 仓库中的提交记录保存的是你的目录下所有文件的快照,就像是把整个目录复制,然后再粘贴一样,但比复制粘贴优雅许多!Git 希望提交记录尽可能地轻量,因此在你每次进行提交时,它并不会盲目地复制整个目录。条件允许的情况下,它会将当前版本与仓库中的上一个版本进行对比,并把所有的差异打包到一起作为一个提交记录。Git 还保存了提交的历史记录。这也是为什么大多数提交记录的上面都有父节点的原因。 当我们使用 add 命令将工作区提交到暂存区,而暂存区其实保存的是当前文件的一个状态,其中包括有哪些目录和文件,以及其对应的大小和内容等信息。但是我们最终是需要将其提交到代码仓库(本地)的,而其命令就是 git commit 了。
#左边执行 $gitcommit-m"1stcommit" $gitcat-file-t6e4a700#查看commit对象的类型 $gitcat-file-p6e4a700#查看commit对象的内容 $gitcat-file-t64d6ef5#查看tree对象的类型 $gitcat-file-p64d6ef5#查看tree对象的内容 #右边执行 $watch-n1-dtree.git
#左边执行 $cat.git/refs/heads/master $cat.git/HEAD #右边执行 $watch-n1-dtree.git
加深理解 commit 提交
执行完成了 git commit 命令,究竟做了什么呢? 当我们再次对 file2.txt 文件的内容进行变更、添加以及提交之后,发现在提交的时候,查看的 commit 对象的内容时,其包含有父节点的 commit 信息。而对于理解的话,可以看看下面的这个提交流程图。#左边执行 $echo"file2.txt">file2.txt $gitstatus $gitaddfile2.txt $gitls-files-s $gitcat-file-p0ac9638 $gitcommit-m"2ndcommit" $gitcat-file-pbab53ff $gitcat-file-p2f07720 #右边执行 $watch-n1-dtree.git
#左边执行 $mkdirfloder1 $echo"file3">floder1/file3.txt $gitaddfloder1 $gitls-files-s $gitcommit-m"3rdcommit" $gitcat-file-p1711e01 $gitcat-file-p9ab67f8 #右边执行 $watch-n1-dtree.git
文件的生命周期状态
总结一下,Git 里面的文件状态和如何切换。 现在,我们已经基本理解了文件如何在工作区、暂存区以及代码仓库之间进行状态的跟踪和同步。在 Git 的操作中,文件的可能状态有哪些,以及如何进行状态切换的,我们这里一起总结一下!
Branch 和 HEAD 的意义
执行完成了 git branch 命令,究竟做了什么呢? 到底什么是分支?分支切换又是怎么一回事?我们通过查看 Git 的官方文档,就可以得到,分支就是一个有名字的(master/dev)指向 commit 对象的一个指针。 我们在初始化仓库的时候,提供会默认给我们分配一个叫做 master 的分支(在最新的版本默认仓库已经变更为 main 了),而 master 分支就是指向最新的一次提交。为什么需要给分支起名字呢?就是为了方便我们使用和记忆,可以简单理解为 alias 命令的意义一致。
#左边执行 $cat.git/HEAD $cat.git/refs/heads/master $gitcat-file-t1711e01 #右边执行 $glo=gitlog
分支操作的背后逻辑
执行完成了 git branch 命令,究竟做了什么呢? 这里我们可以看到分支切换之后,HEAD 指向发生变动了。#左边执行 $gitbranch $gitbranchdev $ll.git/refs/heads $cat.git/refs/heads/master $cat.git/refs/heads/dev $cat.git/HEAD $gitcheckoutdev $cat.git/HEAD #右边执行 $glo=gitlog
#左边执行 $echo"dev">dev.txt $gitadddev.txt $gitcommit-m"1stcommitfromdevbranch" $gitcheckoutmaster $gitbranch-ddev $gitbranch-Ddev $gitcat-file-t861832c $gitcat-file-p861832c $gitcat-file-p680f6e9 $gitcat-file-p38f8e88 #右边执行 $glo=gitlog
checkout 和 commit 操作
我们一起聊一聊,checkout 和 commit 的操作! 我们执行 checkout 命令的时候,其不光可以切换分支,而且可以切换到指定的 commit 上面,即 HEAD 文件会指向某个 commit 对象。在 Git 里面,将 HEAD 文件没有指向 master 的这个现象称之为 detached HEAD。 这里不管 HEAD 文件指向的是分支名称也好,是 commit 对象也罢,其实本质都是一样的,因为分支名称也是指向某个 commit 对象的。
#左边执行 $gitcheckout6e4a700 $gitlog #右边执行 $glo=gitlog
$gitcheckout-btmp $gitlog
即使可以这样操作,我们也很少使用。还记得我们上一章节创建的 dev 分支吗?我们创建了该分支并有了一个新的提交,但是没有合并到 master 分支就直接删除了。现在再使用 log 命令查看的话,是看不到了。 实际,真的看不到了吗?大家要记住,在 Git 里面任何的操作,比如分支的删除。它只是删除了指向某个特定 commit 的指针引用而已,而那个 commit 本身并不会被删除,即 dev 分支的那个 commit 提交还是在的。 那我们怎么找到这个 commit 呢?找到之后,我们就可以在上面继续工作,或者找到之前的文件数据等。 第一种方法:-
[费劲不太好,下下策]
-
在 objects 目录下面,自己一个一个看,然后切换过去。
-
[推荐的操作方式]
-
使用 Git 提供的 git reflog 专用命令来查找。
-
该命令的作用就是用于将我们之前的所有操作都记录下来。
#左边执行 $gitreflog $gitcheckout9fb7a14 $gitcheckout-bdev #右边执行 $glo=gitlog
聊聊 diff 的执行逻辑
当我们执行 diff 命令之后,Git 的逻辑它们是怎么对比出来的呢? 就在本节中中,我们使用上节的仓库,修改文件内容之后,看看 diff 命令都输出了哪些内容呢?我们这里一起来看看,研究研究!$echo"hello">file1.txt $gitdiff $gitcat-file-p42d9955 $gitcat-file-pce01362 #下述命令原理也是一样的 $gitdiff--cached $gitdiffHEAD
Git 如何添加远程仓库
如何将我们本地的仓库和远程服务器上面的仓库关联起来呢? 初始化仓库$gitinit $gitaddREADME.md $gitcommit-m"firstcommit"
关联远程仓库 当我们使用上述命令来关联远程服务器仓库的时候,我们本地 .git 目录也是会发生改变的。通过命令查看 .git/config 文件的话,可以看到配置文件中出现了[remote]字段。#关联远程仓库 $gitremoteaddorigingit@github.com:escapelife/git-demo.git
➜cat.git/config [core] repositoryformatversion=0 filemode=true bare=false logallrefupdates=true ignorecase=true precomposeunicode=true [remote"origin"] url=git@github.com:escapelife/git-demo.git fetch=+refs/heads/*:refs/remotes/origin/*
推送本地分支 当我们执行如下命令,将本地 master 分支推送到远程 origin 仓库的 master 分支。之后,我们登陆 GitHub 就可以看到推送的文件及目录内容了。 推送分支内容的时候,会列举推送的 objects 数量,并将其内容进行压缩,之后推送到我们远程的 GitHub 仓库,并且创建了一个远程的 master 分支(origin 仓库)。#推送本地分支 $gitpush-uoriginmaster
推送之后,我们可以发现,本地的 .git 生成了一些文件和目录,它们都是什么呢?如下所示,会新增四个目录和两个文件,皆为远程仓库的信息。当我们通过命令查看 master 这个文件的内容时,会发现其也是一个 commit 对象。此时与我们本地 master 分支所指向的一致。而其用于表示远程仓库的当前版本,用于和本地进行区别和校对的。➜tree.git ├──logs │├──HEAD │└──refs │├──heads ││├──dev ││├──master ││└──tmp │└──remotes#新增目录 │└──origin#新增目录 │└──master#新增文件 └──refs ├──heads │├──dev │├──master │└──tmp ├──remotes#新增目录 │└──origin#新增目录 │└──master#新增文件 └──tags
远程仓库存储代码
使用 GitLab 来了解远程仓库的服务器到底是如何存储,我们的代码的! 当我们编写完代码之后,将其提交到对应的远程服务器上面,其存储结构和我们地址是一模一样的。如果我们仔细想想的话,不一样的话才见怪了。 Git 本来就是代码的分发平台,无中心节点,即每个节点都是主节点,所以其存储的目录结构都是一直的。这样,不管哪一个节点的内容发生丢失或缺失的话,我们都可以通过其他节点来找到。而 Git 服务器就是一个可以帮助我们,实时都可以找到的节点,而已。 原文链接:https://www.escapelife.site/posts/da89563c.html-
存储
+关注
关注
13文章
4296浏览量
85796 -
代码
+关注
关注
30文章
4779浏览量
68516 -
Git
+关注
关注
0文章
198浏览量
15755
原文标题:Git 基本原理介绍
文章出处:【微信号:magedu-Linux,微信公众号:马哥Linux运维】欢迎添加关注!文章转载请注明出处。
发布评论请先 登录
相关推荐
评论