git describe功能实现

一、describe的功能

git的提交版本号是一个hash值,所以版本号本身没有太大意义,这显然不太符合大家的认知习惯,就像IP地址没有域名有意义一样的原理。但是如何描述这些commitid呢?git鼓励大家频繁创建新的分线,这样可以结合分支名称来描述某个提交记录,这个就是git describe的主要功能。

二、ref列表的获取

1、ref直接文件和子目录的添加

这个其实比较简单,就是遍历我们常见的.git/ref文件夹下的所有文件,这些文件夹下的文件名对应分支名称,文件中的内容对应commitid。
函数名中的loose就是常规的文件系统文件,loose相对于通过git pack-refs命令压缩之后的ref。
/*
* Read the loose references from the namespace dirname into dir
* (without recursing). dirname must end with '/'. dir must be the
* directory entry corresponding to dirname.
*/
static void loose_fill_ref_dir(struct ref_store *ref_store,
struct ref_dir *dir, const char *dirname)
{
……
while ((de = readdir(d)) != NULL) {
……
} else if (S_ISDIR(st.st_mode)) {
strbuf_addch(&refname, '/');
add_entry_to_dir(dir,
create_dir_entry(dir->cache, refname.buf,
refname.len, 1));
} else {
if (!refs_resolve_ref_unsafe(&refs->base,
refname.buf,
RESOLVE_REF_READING,
&oid, &flag)) {
oidclr(&oid);
flag |= REF_ISBROKEN;
}
……
}

2、ref文件夹下文件的递归遍历

/*
* Load all of the refs from `dir` (recursively) that could possibly
* contain references matching `prefix` into our in-memory cache. If
* `prefix` is NULL, prime unconditionally.
*/
static void prime_ref_dir(struct ref_dir *dir, const char *prefix)
{
/*
* The hard work of loading loose refs is done by get_ref_dir(), so we
* just need to recurse through all of the sub-directories. We do not
* even need to care about sorting, as traversal order does not matter
* to us.
*/
int i;
for (i = 0; i < dir->nr; i++) {
struct ref_entry *entry = dir->entries[i];
if (!(entry->flag & REF_DIR)) {
/* Not a directory; no need to recurse. */
} else if (!prefix) {
/* Recurse in any case: */
prime_ref_dir(get_ref_dir(entry), NULL);
} else {
switch (overlaps_prefix(entry->name, prefix)) {
case PREFIX_CONTAINS_DIR:
/*
* Recurse, and from here down we
* don't have to check the prefix
* anymore:
*/
prime_ref_dir(get_ref_dir(entry), NULL);
break;
case PREFIX_WITHIN_DIR:
prime_ref_dir(get_ref_dir(entry), prefix);
break;
case PREFIX_EXCLUDES_DIR:
/* No need to prime this directory. */
break;
}
}
}
}

三、获取名字列表

可以看到,主要是通过遍历refs文件夹下所有引用(可能是tag,也可能包括heades、remotes),获得所有的commitid对应的文件名。由于遍历是“文件夹名=>commitid”路径,所以这里要做一个翻转,建立从commitid到ref名字的hash映射。
git-master\builtin\describe.c
static int get_name(const char *path, const struct object_id *oid, int flag, void *cb_data)
{
int is_tag = 0;
struct object_id peeled;
int is_annotated, prio;
const char *path_to_match = NULL;

if (skip_prefix(path, "refs/tags/", &path_to_match)) {
is_tag = 1;
} else if (all) {
if ((exclude_patterns.nr || patterns.nr) &&
!skip_prefix(path, "refs/heads/", &path_to_match) &&
!skip_prefix(path, "refs/remotes/", &path_to_match)) {
/* Only accept reference of known type if there are match/exclude patterns */
return 0;
}
} else {
/* Reject anything outside refs/tags/ unless --all */
return 0;
}
……
}

四、遍历commit链表

遍历commit链表,比较是否有和commitid相同的ref,如果有则匹配成功。其中的seen_commits记录的是反向的层级。
static void describe_commit(struct object_id *oid, struct strbuf *dst)
{
……
list = NULL;
cmit->object.flags = SEEN;
commit_list_insert(cmit, &list);
while (list) {
struct commit *c = pop_commit(&list);
struct commit_list *parents = c->parents;
struct commit_name **slot;

seen_commits++;
slot = commit_names_peek(&commit_names, c);
n = slot ? *slot : NULL;
if (n) {
if (!tags && !all && n->prio < 2) {
unannotated_cnt++;
} else if (match_cnt < max_candidates) {
struct possible_tag *t = &all_matches[match_cnt++];
t->name = n;
t->depth = seen_commits - 1;
t->flag_within = 1u << match_cnt;
t->found_order = match_cnt;
c->object.flags |= t->flag_within;
if (n->prio == 2)
annotated_cnt++;
}
else {
gave_up_on = c;
break;
}
}
……

}

五、最终的描述格式

ref标签版本号,深度(),(提交id)
static void append_suffix(int depth, const struct object_id *oid, struct strbuf *dst)
{
strbuf_addf(dst, "-%d-g%s", depth, find_unique_abbrev(oid, abbrev));
}

六、以git的代码库为例

1、代码库

更新之后最新版本为cefe983a320c03d7843ac78e73bd513a27806845,然后回退到
tsecer:harry: git checkout b83e1310297c7440d962bed10a198f4ccf2c7e0d
Note: switching to 'b83e1310297c7440d962bed10a198f4ccf2c7e0d'.
显示内容为:其中的533应该是从指定待描述commitid找到提交记录总共遍历的commit层级。
tsecer:harry: git describe --all HEAD
tags/v2.33.0-533-gb83e131029
tsecer:harry: git describe --all HEAD --debug
describe HEAD
No exact match on refs or tags, searching to describe
annotated 533 tags/v2.33.0
annotated 519 tags/v2.33.0-rc2
annotated 526 tags/v2.33.0-rc1
annotated 585 tags/v2.33.0-rc0
annotated 1048 tags/v2.32.0
annotated 1084 tags/v2.32.0-rc3
annotated 1088 tags/v2.32.0-rc2
annotated 1120 tags/v2.32.0-rc1
annotated 1159 tags/v2.32.0-rc0
annotated 1709 tags/v2.31.1
traversed 9043 commits
more than 10 tags found; listed 10 most recent
gave up search at a5828ae6b52137b913b978e16cd2334482eb4c1f
tags/v2.33.0-533-gb83e131029

2、例子

可以看到,describe中输出的118就是相对于当前版本的到达父节点的层级深度。
tsecer:harry: git describe --all --first-parent HEAD
tags/v2.33.0-118-gb83e131029
tsecer:harry: git log -n 1 --skip=118 --first-parent HEAD
commit 225bc32a989d7a22fa6addafd4ce7dcd04675dbf (tag: v2.33.0, origin/maint)
Author: Junio C Hamano <gitster@pobox.com>
Date: Mon Aug 16 12:15:44 2021 -0700

Git 2.33

Signed-off-by: Junio C Hamano <gitster@pobox.com>

harrywfli@harrywfli-PC3 MINGW64 /g/gitsrc/git ((b83e131029...))
tsecer:harry:

 

posted on 2021-09-30 16:51  tsecer  阅读(1120)  评论(0编辑  收藏  举报

导航