zoukankan      html  css  js  c++  java
  • 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-masteruiltindescribe.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:

  • 相关阅读:
    Codeforces Round #654 (Div. 2)A-E1
    android 学习receiver和发送广播,其中监听其他activity的启动demo;给activity加自定义权限只有指定有权限的app可以监听到
    任务栈Task的模式
    Activity生命周期学习笔记,和横竖切屏时候activity销毁时候保存数据和调用的方法
    Activity之间利用intent单个传递数据和批量传递数据
    Android学习-启动服务startActivityForResult调用activity并覆写onActivityResult()接收返回来的信息
    android学习之intent学习笔记
    android断点下载并显示进度,关于handler,和主线程不能联网采取子线程联网下载,和多线程下载学习
    Contentprovider学习笔记
    android学习之LayoutInflater的用法,在myAdapter getView()里将多个TextView组件压缩成一个View控件,并在listView里显示
  • 原文地址:https://www.cnblogs.com/tsecer/p/15357495.html
Copyright © 2011-2022 走看看