git学习之进阶原理篇

本文详细介绍了Git的分支原理,包括如何新建、查看和删除分支,以及远程分支的概念和操作。此外,还深入探讨了Git的内部机制,如数据对象(blob, tree, commit)和对象存储。特别是变基操作,解释了其工作原理和风险,以及在不同分支间的应用。通过学习,读者将能够更深入地理解和掌握Git的高级用法。" 109583674,9935697,Java入门:运算符详解,"['Java', '运算符', '基础教程']

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

目录

分支的原理

远程分支

变基

git内部原理

数据对象(blob object)

树对象(tree object)

提交对象(commit object)

对象存储


本篇的内容,来自于对《Pro Git》这本书学习的总结。

分支的原理

假设现在有一个工作目录,里面包含了三个将要被暂存和提交的文件。 暂存操作会为每一个文件计算校验和(使用SHA-1 哈希算法),然后会把当前版本的文件快照保存到Git 仓库中(Git 使用 blob 对象来保存它们),最终将校验和加入到暂存区域等待提交:

$ git add README test.rb LICENSE

$ git commit -m 'The initial commit of my project'

当使用 git commit 进行提交操作时,Git 会先计算每一个子目录(本例中只有项目根目录)的校验和,然后在Git 仓库中这些校验和保存为树对象。 随后,Git 便会创建一个提交对象,它除了包含上面提到的那些信息外,还包含指向这个树对象(项目根目录)的指针。如此一来,Git 就可以在需要的时候重现此次保存的快照。

现在,Git 仓库中有五个对象:三个 blob 对象(保存着文件快照)、一个树对象(记录着目录结构和 blob 对象索引)以及一个提交对象(包含着指向前述树对象的指针和所有提交信息)。Git 的分支,其实本质上仅仅是指向提交对象的可变指针。

 

$ git checkout -b iss53

新建一个分支并同时切换到那个分支上

等效于:

$ git branch iss53

$ git checkout iss53

 

Git 的默认分支名字是 master。 它会在每次的提交操作中自动向前移动。

git branch 查看分支

git branch -v 如果需要查看每一个分支的最后一次提交。--merged 与 --no-merged 这两个有用的选项可以过滤这个列表中已经合并或尚未合并到当前分支的分支。

git branch -d testing 删除分支。当分支包含了还未合并的工作,尝试使用 git branch -d 命令删除它时会失败,使用git branch -D testing可以强制删除。

$ git merge iss53 分支合并

遇到冲突时,Git 会暂停下来,等待你去解决合并产生的冲突。你可以在合并冲突后的任意时刻使用 git status 命令来查看那些因包含合并冲突而处于未合并(unmerged)状态的文件 。解决文件里的冲突后,使用 git add来将其标记为冲突已解决,然后再git commit 来完成合并提交

$ git merge --no-ff -m "merged bug fix 101" iss53 选项“--no--ff”关闭快速合并,默认开启了快速合并的选项

 

远程分支

远程跟踪分支是远程分支状态的引用。 它们是你不能移动的本地引用,当你做任何网络通信操作时,它们会自动移动。它们以 (remote)/(branch) 形式命名。

$ git push origin serverfix:awesomebranch 来将本地的 serverfix 分支推送到远程仓库上的 awesomebranch 分支

$ git checkout -b serverfix origin/serverfix(或git checkout --track origin/serverfix) 基于远程分支,建一个自己的新分支

$ git branch -u origin/serverfix

设置已有的本地分支跟踪一个刚刚拉取下来的远程分支,或者想要修改正在跟踪的上游分支,你可以在任意时间使用 -u 或 --set-upstream-to 选项运行 git branch 来显式地设置。当设置好跟踪分支后,可以通过 @{upstream} 或 @{u} 快捷方式来引用它。

$ git branch --set-upstream-to=origin/dev dev 指定本地 dev 分支与远程 origin/dev 分支的链接

git branch -vv 查看所有本地分支及设置的跟踪分支

git fetch --all 抓取所有的远程仓库

git fetch 拉取分支

git pull 拉取并自动合并分支

git push origin --delete serverfix 删除一个远程分支

 

变基

变基是将一系列提交按照原有次序依次应用到另一分支上,而合并是把最终结果合在一起。一般是为了确保在向远程分支推送时能保持提交历史的整洁。

 

假如有2个分支master和experiment,将 C4 中的修改变基到 C3 上,操作如下:

$ git checkout experiment

$ git rebase master

 

它的原理是首先找到这两个分支(即当前分支 experiment、变基操作的目标基底分支 master)的最近共同祖先 C2,然后对比当前分支相对于该祖先的历次提交,提取相应的修改并存为临时文件,然后将当前分支指向目标基底 C3, 最后以此将之前另存为临时文件的修改依序应用。

现在回到 master 分支,进行一次快进合并

$ git checkout master

$ git merge experiment

发生rebase冲突时,

git add 更新这些内容的索引

git rebase --continue 继续应用(apply)余下的补丁

git rebase --abort 终止rebase的操作,回到rebase开始前的状态

 

在对两个分支进行变基时,所生成的“重放”并不一定要在目标分支上应用,你也可以指定另外的一个分支进行应用。

 

git rebase --onto master server client

选中在 client 分支里但不在server 分支里的修改(即 C8 和 C9),将它们在 master 分支上重放,之后,再切换到master分支,进行快进合并。

 

git rebase master server   将 server 中的修改变基到 master 上。这样做能省去你先切换到server 分支,再对其执行变基命令的多个步骤。

变基操作的实质是丢弃一些现有的提交,然后相应地新建一些内容一样但实际上不同的提交。

变基的风险:不要对在你的仓库外有副本的分支执行变基

 

git内部原理

.git目录下有4个重要的目录/文件。

$ ll .git/
total 18
-rw-r--r-- 1 kuang 197609  13  8月  5 23:01 COMMIT_EDITMSG
-rw-r--r-- 1 kuang 197609 130  7月 26 10:23 config
-rw-r--r-- 1 kuang 197609  73  7月 26 10:23 description
-rw-r--r-- 1 kuang 197609  23  8月  5 23:01 HEAD
drwxr-xr-x 1 kuang 197609   0  7月 26 10:23 hooks/
-rw-r--r-- 1 kuang 197609 209  8月  5 23:01 index
drwxr-xr-x 1 kuang 197609   0  7月 26 10:23 info/
drwxr-xr-x 1 kuang 197609   0  7月 26 12:29 logs/
drwxr-xr-x 1 kuang 197609   0  8月  5 23:01 objects/
-rw-r--r-- 1 kuang 197609  41  7月 29 23:18 ORIG_HEAD
drwxr-xr-x 1 kuang 197609   0  7月 26 10:23 refs/

$ cat .git/HEAD
ref: refs/heads/master

objects 目录存储所有数据内容;

refs 目录存储指向数据(分支)的提交对象的指针;

HEAD 文件指向目前被检出的分支;

index 文件保存暂存区信息。

 

数据对象(blob object)

Git 是一个内容寻址文件系统。这意味着,Git 的核心部分是一个简单的键值对数据库(key-value data store)。

$ echo 'test content' | git hash-object -w --stdin

d670460b4b4aece5915caf5c68d12f560a9fe3e4

或者

$ git hash-object -w test.txt

83baae61804e65cc73a7201a7252750c76066a30

git hash-object命令可将任意数据保存于 .git 目录,并返回相应的键值。该命令输出一个长度为 40 个字符的校验和。 这是一个 SHA-1 哈希值——一个将待存储的数据外加一个头部信息(header)一起做 SHA-1 校验运算而得的校验和。

$ git cat-file -p d670460b4b4aece5915caf5c68d12f560a9fe3e4

test content

git cat-file 指定 -p 选项可指示该命令自动判断内容的类型,并为我们显示格式友好的内容。利用 cat-file -t 命令,可以让 Git 告诉我们其内部存储的任何对象类型

 

树对象(tree object)

它能解决文件名保存的问题,也允许我们将多个文件组织到一起。

git cat-file -p master^{tree}

master^{tree} 语法表示 master 分支上最新的提交所指向的树对象。

 

通常,Git 根据某一时刻暂存区(即 index 区域,下同)所表示的状态创建并记录一个对应的树对象。创建一个树对象,首先需要通过暂存一些文件来创建一个暂存区。 可以通过底层命令 update-index 为一个单独文件创建一个暂存区。可以通过 write-tree 命令将暂存区内容写入一个树对象。

此外,还可以通过调用 read-tree 命令,可以把树对象读入暂存区。 本例中,可以通过对 read-tree 指定 --prefix 选项,将一个已有的树对象作为子树读入暂存区

 

提交对象(commit object)

可以通过调用 commit-tree 命令创建一个提交对象,为此需要指定一个树对象的 SHA-1 值,以及该提交的父提交对象(如果有的话)。

$ echo 'first commit' | git commit-tree d8329f

fdf4fc3344e67ab068f836878b6c4951e3b15f3d

 

Git 所做的实质工作——将被改写的文件保存为数据对象,更新暂存区,记录树对象,最后创建一个指明了顶层树对象和父提交的提交对象。 这三种主要的 Git 对象——数据对象、树对象、提交对象——最初均以单独文件的形式保存在 .git/objects 目录下。 下面列出了目前示例目录内的所有对象,辅以各自所保存内容的注释:

$ find .git/objects -type f

.git/objects/01/55eb4229851634a0f03eb265b69f5a2d56f341 # tree 2

.git/objects/1a/410efbd13591db07496601ebc7a059dd55cfe9 # commit 3

.git/objects/1f/7a7a472abf3dd9643fd615f6da379c4acb3e3a # test.txt v2

.git/objects/3c/4e9cd789d88d8d89c1073707c3585e41b0e614 # tree 3

.git/objects/83/baae61804e65cc73a7201a7252750c76066a30 # test.txt v1

.git/objects/ca/c0cab538b970a37ea1e769cbbde608743bc96d # commit 2

.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4 # 'test content'

.git/objects/d8/329fc1cc938780ffdd9f94e0d364e0ea74f579 # tree 1

.git/objects/fa/49b077972391ad58037050f2a75f74e3671e92 # new.txt

.git/objects/fd/f4fc3344e67ab068f836878b6c4951e3b15f3d # commit 1

如果跟踪所有的内部指针,将得到一个类似下面的对象关系图:

 

对象存储

Git 以对象类型作为开头来构造一个头部信息,本例中是一个“blob”字符串。 接着 Git 会添加一个空格,随后是数据内容的长度,最后是一个空字节(null byte):

>> header = "blob #{content.length}\0"

=> "blob 16\u0000"

Git 会将上述头部信息和原始数据拼接起来,并计算出这条新内容的 SHA-1 校验和。

Git 会通过 zlib 压缩这条新内容。最后,需要将这条经由 zlib 压缩的内容写入磁盘上的某个对象。 要先确定待写入对象的路径(SHA-1 值的前两个字符作为子目录名称,后 38 个字符则作为子目录内文件的名称)。

就这样创建了一个有效的 Git 数据对象。 所有的 Git 对象均以这种方式存储,区别仅在于类型标识——另两种对象类型的头部信息以字符串“commit”或“tree”开头,而不是“blob”。 另外,虽然数据对象的内容几乎可以是任何东西,但提交对象和树对象的内容却有各自固定的格式。


 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值