You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
3.读取tree的id
注意,这里的tree的id可以多个,前面讲到了3-Way Merge,所以tree最多可以有3个!
这里用stage变量计数
stage=2:Single Tree Merge
stage=3:Two Tree Merge
stage=4:3-Way Merge
这里我们可以把后两种情况先放一放,比较不符合我“计划”的场景。
通过调用get_sha1来实现获取tree的id
a. 尝试读取40位的sha1来获取
b. 尝试通过name匹配读取refs中的内容,再从refs的文件中获取相关sha1的id
staticconstmerge_fn_tmerge_function[] = {
[1] =oneway_merge,
[2] =twoway_merge,
[3] =threeway_merge,
};
merge_fn_tfn;
if (stage<2||stage>4)
die("just how do you expect me to merge %d trees?", stage-1);
if (emu23&&stage!=3)
die("--emu23 takes only two trees");
fn=merge_function[stage-1];
if (stage==3&&emu23) {
setup_emu23();
fn=merge_function[3];
}
merge_cache(active_cache, active_nr, fn);
上一节里面fn就是merge算法的指针。其他的Merge算法太难。我们先挑最简单的逻辑先深入
Single Tree Merge算法是通过oneway_merge方法实现的
/* * One-way merge. * * The rule is: * - take the stat information from stage0, take the data from stage1 */staticintoneway_merge(structcache_entry**src, structcache_entry**dst,
structcache_entry**next, inttail)
{
structcache_entry*old=src[0];
structcache_entry*a=src[1];
if (src[2] ||src[3])
return-1;
if (!a)
return0;
if (old&&same(old, a)) {
*dst++=old;
return1;
}
returnmerged_entry(a, NULL, dst);
}
下一个目标是谁?
是merge还是rebase?因为我觉得这两个概念还是太难。还没到火候。
目的我对git checkout 比较感兴趣,git是怎么方便的魔术般快速的切换版本的?
git checkout有什么?
首先git checkout也是一个shell脚本
具体内存主要是git-read-tree + git-checkout-cache
想不到这么快就和git-read-tree见面了!
当然这打脸也是相当的快,其实git-read-tree比git-write-tree要复杂的多,虽然有一部分确实是反向逻辑,但是还有很多“机关”在都之前都完全没享到
git-read-tree 文档概况
还是先看文档比较好
功能大概是:Reads tree information into the directory cache
嗯,从已经持有化的对象数据库 tree -> cache ,算是write-tree的反向操作。
但是后面文档有大量的篇幅来讲merge!
这下一下子把我给整不会了啊,按照套路来,应该不是果然read-tree和write-tree一样反向操作,看懂递归一切OK了嘛。
原来git-read-tree的核心不在于read而是merge
git-read-tree 代码梳理1
还是从read-tree.c的main函数入口开始
解析参数
主要是-或者--参数读取,设置标志位(略)
重新加载cache并去除unmerged的cache
主要用read_cache_unmerged方法实现,先调用read_cache,很熟悉了
再检查 ce_stage的标志位,有没有unmerge的,如果有则删除。
代码还比较简单
先赋值cache_entry **dst;
一次遍历,如果有删除后续项直接“补位”
3.读取tree的id
注意,这里的tree的id可以多个,前面讲到了3-Way Merge,所以tree最多可以有3个!
这里用stage变量计数
stage=2:Single Tree Merge
stage=3:Two Tree Merge
stage=4:3-Way Merge
这里我们可以把后两种情况先放一放,比较不符合我“计划”的场景。
通过调用get_sha1来实现获取tree的id
a. 尝试读取40位的sha1来获取
b. 尝试通过name匹配读取refs中的内容,再从refs的文件中获取相关sha1的id
这里算是符合read-tree的“中心思想”了,但是其实只是一个中间步骤!后面慢慢讲
这里是调用unpack_tree来实现
a)调用read_object_with_reference
这里简化下过程通过find_sha1_file函数找到对应实体文件
map_sha1_file_internal把文件内容读入内存
unpack_sha1_file把内容解压出来,
先调用unpack_sha1_header->parse_sha1_header解析出对象的type和length,我们所有对象的head都是{type} {length}。所以我们通过一次遍历完成解析
unpack_sha1_rest把其他内容也一并解压出来
b)调用read_tree
这里和write_tree的逻辑正好相反
里面用read_tree_recursive递归实现,也有base和baselen的概念
这里不展开了,代码还是很精彩的,有兴趣的同学还是可以亲自读一下
注:主要区别在于,这里read_tree,多了一个stage的参数。
这里tree主要是遍历,read_one_entry把真正的cache_entry对象扔到cache里面去
这里再次注意stage
ce->ce_flags = create_ce_flags(baselen + len, stage);
被边进了ce_flags里面去了。
本节代码要点
read_tree可以读入好几颗(1-3),这样意味着同样的filename可能有多个版本都“共存”在cache中
不同的的版本用stage区分,stage被记录到ce_flags 中
这里ce_flags成了cache_entry的关键属性了,我需要扒一扒看看。
ce_flags究竟有啥用?
其实ce_flags已经是老朋友了,第一篇就碰到了,但是对其作用搞不清楚也只能skip了。
单看这段代码有点懵,就是左移12位的stage 和 name的length合并在一起存ce_flags
这里稍微通一点,就是说我们的名字长度不可能太长(毕竟太长不现实)
名字长度最多我们用0x0fff。
再直白一点就是双字节存储16位,使用掩码0000111111111111,就是只用12位。那还有4位给谁用那?
这回差不多看出来了吧
0x3000=0011000000000000做&掩码正好取了第一个字节的低字节。
再执行>> CE_STAGESHIFT,正好把右方的零“清掉”
CE_STAGEMASK 为啥只取2位?
容量刚刚好stage0~stage3。正好对应3-Way Merge。真是一点都不浪费!一个字段n用
剩下还有两个“位”存了什么信息?
还有一个#define CE_UPDATE (0x4000),把第二位给“占”了,暂时还不知道有啥用
”最高位“应该是保留了吧,并没有看到0x8000的MASK。
数形结合再总结理解下
一图解千愁
stage是做啥?
绕了半天ce_stage可以从ce_flags“独立”出来了。一个blob分了好多个版本其实是没有意义的,最终,“分久必合”,这是合并前的准备。下面一节我们开始讲merge_cache
git-read-tree 代码梳理2:初见merge_cache
接下来是merge的主逻辑啦
merge初始化操作
可以看到--emu23应该是有特殊处理,到stage3但是用的stage4 threeway_merge逻辑处理
merge_function正好存了三种merge的方法。
我们再进入到merge_cache函数看下merge逻辑。
merge_cache方法详解
初始化目标指针**dst
尾部数量tail
开始遍历
初始化变量
int entries:本次merge的数量具体后面讲
name:本次遍历的基础值,比较name对应的path都一样的值进入merge阶段
ce:和name比较动态指针,如果一样存入待merge数组
stages[4]:本次循环的merge数组,因为最多3树合并,加上本来的cache,满打满算4,所以数组直接设4
for循环里面收集 stages待merge元素,
只要path_matches就继续for循环进stages
merge所有stages的元素
根据合并的情况,dst指向移动,active_nr增值
这里有必要图文讲解了,你再结合上面的代码就一下通透了
其实这里merge_cache的dst指针就是把原来的cache的整个内存“重新分配”了
这里再次感谢下cache_entry的name排序,让我们可以把相同路径的元素“自然”的凑在一起
entries就是合并结果
小于0:拒绝合并并退出,这里不展开套路
大于0:就是合并完的个数,目标都是1,暂时还没看到大于1的情况
等于0:其实就是合并结果是删除,这种情况也常见,这种情况dst指针就不动了,等下个元素加入再动
Single Tree Merge算法
上一节里面fn就是merge算法的指针。其他的Merge算法太难。我们先挑最简单的逻辑先深入
Single Tree Merge算法是通过oneway_merge方法实现的
注释也说很明确了,以stage1的数据为准
如果,新的树里面没这个entry,entries就直接返回0,表示删除
如果,新老cache一样,那么就无所谓新旧保留老的cache
如果新老不一,无脑用新,再调用merged_entry。
我们再看下merged_entry做了啥
merge->ce_flags |= htons(CE_UPDATE);
ce_flags 加上CE_UPDATE标志位,就是左起第2位。
merge->ce_flags &= ~htons(CE_STAGEMASK);
把stage的标志位清零
*dst++ = merge;
把合入的cache放入dst
尾声
最后就是回写index的逻辑,调用write_cache和commit_index_file
和update-cache的逻辑一模一样。略
总结查漏补缺
read-tree的核心在于merge而非read
目前我们选择了最简场景。未来merge可能这里还有再深入。
如果使用checkout切换到某个commit,就是先调用git-read-tree
等下!不是前面git-read-tree的参数是 tree-id 不是commit-id啊!我哪里搞错了
再看下文档里面教程里面也是用git-read-tree HEAD,HEAD里面也应该是commit的id啊。
一定是遗漏了啥
再读read_object_with_reference函数,发现里面有个const char *required_type
再看一遍,就是读出的对象和required_type完全一致就直接返回
如果不是,那就可能是应用对象commit或者tag
接下来把内容再截取一下
就是把“tree ”去头(红色部分),往后截取40个字符,获取新的sha1,直到读到tree为止。
OK,逻辑闭环了。只是这文档有点粗糙,应该是tree id或者是他的引用吧。
merge逻辑充分考虑性能
比如我checkout一个变化不是很大的commit,通过目前的算法速度非常快。不是以前“想象中”的“全进全出”。
read tree,而cache不真正存tree
非常有意思,心中有tree,手中无tree。
The text was updated successfully, but these errors were encountered: