【git安装教程】 Git引用系统:分支、标签与HEAD的内部工作原理

Git引用系统:分支、标签与HEAD的内部工作原理

【免费下载链接】git Git Source Code Mirror - This is a publish-only repository but pull requests can be turned into patches to the mailing list via GitGitGadget (https://gitgitgadget.github.io/). Please follow Documentation/SubmittingPatches procedure for any of your improvements. 【免费下载链接】git 项目地址: https://gitcode.com/GitHub_Trending/gi/git

本文深入解析了Git引用系统的核心机制,包括引用存储的物理结构、命名空间设计、分支引用与HEAD指针管理、标签对象实现以及引用事务与并发控制机制。文章详细探讨了Git如何通过层次化的目录结构、严格的名称验证和原子性事务操作来确保引用管理的高效性和可靠性,为理解Git内部工作原理提供了全面的技术视角。

引用存储机制与命名空间设计

Git的引用系统采用层次化的命名空间设计,通过精心组织的目录结构来实现高效、安全的引用管理。这种设计不仅保证了引用的唯一性和可读性,还为Git的各种功能提供了坚实的基础架构支持。

引用存储的物理结构

Git引用在文件系统中的存储采用分层目录结构,所有引用都存储在.git/refs目录下。这种设计使得引用管理既直观又高效:

mermaid

每个引用实际上是一个文本文件,包含对应提交的SHA-1哈希值。例如,.git/refs/heads/master文件内容可能是:

a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0

命名空间的核心设计

Git定义了标准化的命名空间枚举,在refs.c中通过ref_namespace数组实现:

enum ref_namespace {
    NAMESPACE_HEAD,
    NAMESPACE_BRANCHES,      // refs/heads/
    NAMESPACE_TAGS,          // refs/tags/
    NAMESPACE_REMOTE_REFS,   // refs/remotes/
    NAMESPACE_STASH,         // refs/stash
    NAMESPACE_REPLACE,       // refs/replace/
    NAMESPACE_NOTES,         // refs/notes/
    NAMESPACE_PREFETCH,      // refs/prefetch/
    NAMESPACE_REWRITTEN,     // refs/rewritten/
};

每个命名空间都有特定的用途和装饰标记:

命名空间路径前缀用途装饰标记
分支refs/heads/本地分支管理DECORATION_REF_LOCAL
标签refs/tags/版本标签DECORATION_REF_TAG
远程引用refs/remotes/远程跟踪分支DECORATION_REF_REMOTE
贮藏refs/stash临时工作保存DECORATION_REF_STASH
替换refs/replace/对象替换DECORATION_GRAFTED

引用名称验证机制

Git对引用名称有严格的验证规则,通过refname_disposition数组定义字符处理策略:

static unsigned char refname_disposition[256] = {
    // ASCII控制字符: 禁止使用
    1, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
    4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
    
    // 特殊字符处理
    4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 2, 1,
    
    // 数字和基本字符
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 4,
    
    // 大写字母
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    
    // 小写字母和其他字符
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 0, 4, 0
};

字符处理策略说明:

  • 0: 可接受的引用名称字符
  • 1: 组件结束分隔符
  • 2: 点字符,检查前导点以防止..
  • 3: 左花括号,检查前导@以防止@{
  • 4: 禁止字符(控制字符、:?[\^~等)
  • 5: 星号,仅在引用规范模式中允许

引用事务处理机制

Git通过ref_transaction结构实现原子性的引用更新,确保多个引用操作要么全部成功,要么全部失败:

struct ref_transaction {
    struct ref_store *ref_store;
    struct ref_update **updates;      // 更新操作数组
    struct string_list refnames;      // 涉及的引用名称
    size_t alloc;                     // 分配的大小
    size_t nr;                        // 当前更新数量
    enum ref_transaction_state state; // 事务状态
    struct ref_transaction_rejections *rejections; // 拒绝的更新
    void *backend_data;               // 后端特定数据
    unsigned int flags;               // 事务标志
    uint64_t max_index;               // 最大索引值
};

事务状态机遵循严格的流程:

mermaid

动态命名空间配置

Git支持运行时动态更新命名空间配置,通过update_ref_namespace()函数实现:

void update_ref_namespace(enum ref_namespace namespace, char *ref) {
    struct ref_namespace_info *info = &ref_namespace[namespace];
    if (info->ref_updated)
        free((char *)info->ref);
    info->ref = ref;
    info->ref_updated = 1;
}

这种灵活性使得可以通过环境变量(如GIT_REPLACE_REF_BASE)或配置变量来定制命名空间行为。

引用迭代器设计

Git使用迭代器模式来遍历引用,支持多种迭代策略:

struct ref_iterator {
    struct ref_iterator_vtable *vtable; // 虚函数表
    const char *refname;               // 当前引用名称
    const char *referent;              // 当前引用目标
    const struct object_id *oid;       // 当前对象ID
    unsigned int flags;                // 迭代标志
};

迭代器支持合并迭代、前缀过滤、类型过滤等高级操作,为引用查询提供了强大的基础设施。

Git的引用存储机制和命名空间设计体现了精妙的软件工程思想,通过层次化结构、严格验证和原子操作,为版本控制系统提供了可靠、高效的引用管理能力。这种设计不仅保证了数据的一致性,还为Git的分布式特性提供了坚实的基础支持。

分支引用与HEAD指针管理

在Git的引用系统中,分支引用和HEAD指针是版本控制的核心机制。它们协同工作,为用户提供了直观的分支管理和提交跟踪能力。本节将深入探讨Git如何实现分支引用和HEAD指针的内部管理机制。

分支引用的内部表示

Git中的分支引用存储在.git/refs/heads/目录下,每个分支对应一个文件,文件内容为该分支当前指向的提交对象的SHA-1哈希值。这种设计既简单又高效,使得分支操作变得轻量级。

// refs.c 中的分支引用处理函数
int refs_for_each_branch_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
{
    return refs_for_each_ref_in(refs, "refs/heads/", fn, cb_data);
}

int check_branch_ref(struct strbuf *sb, const char *name)
{
    if (!name || !*name)
        return -1;
    
    strbuf_reset(sb);
    strbuf_addstr(sb, name);
    strbuf_splice(sb, 0, 0, "refs/heads/", 11);
    
    return check_refname_format(sb->buf, 0);
}

Git使用专门的函数来处理分支引用,如refs_for_each_branch_ref()用于遍历所有分支引用,check_branch_ref()用于验证分支引用名称的合法性。

HEAD指针的多重角色

HEAD指针在Git中扮演着至关重要的角色,它可以是:

  1. 符号引用(Symbolic Reference):指向当前分支的引用
  2. 分离头指针(Detached HEAD):直接指向具体的提交对象

mermaid

分支引用创建与更新机制

当创建新分支时,Git会执行以下步骤:

  1. 验证分支名称的合法性
  2. refs/heads/目录下创建对应的引用文件
  3. 将引用文件内容设置为目标提交的SHA-1值
  4. 更新相关的reflog记录
// 分支创建的核心逻辑(简化版)
struct ref_update *create_branch_update(const char *branch_name, 
                                       const struct object_id *target_oid)
{
    struct strbuf refname = STRBUF_INIT;
    strbuf_addf(&refname, "refs/heads/%s", branch_name);
    
    return ref_transaction_add_update(transaction, refname.buf,
                                     REF_HAVE_NEW, target_oid,
                                     &null_oid, NULL, NULL,
                                     committer_info, "branch: Created from HEAD");
}

HEAD指针的状态管理

Git通过检查HEAD文件的内容来确定当前状态:

// 检测HEAD状态的逻辑
int is_detached_head(struct repository *r)
{
    struct object_id oid;
    struct strbuf sb = STRBUF_INIT;
    int detached = 0;
    
    if (read_ref_full("HEAD", 0, &oid, &sb) == 0) {
        // 如果HEAD包含完整的SHA-1值,则是分离状态
        detached = (strlen(sb.buf) == GIT_SHA1_HEXSZ);
    }
    strbuf_release(&sb);
    return detached;
}

引用事务处理

Git使用事务机制来保证引用更新的原子性:

mermaid

分支引用与工作树的关联

Git维护着分支引用与工作树状态之间的紧密关联:

// 获取当前分支名称的函数
const char *resolve_ref_unsafe(const char *refname, int resolve_flags,
                              struct object_id *oid, struct strbuf *referent)
{
    // 解析引用,处理符号引用链
    for (int depth = 0; depth < SYMREF_MAXDEPTH; depth++) {
        if (refs_read_raw_ref(ref_store, refname, oid, referent, 
                             &type, &failure_errno)) {
            break;
        }
        
        if (!(type & REF_ISSYMREF)) {
            return refname;  // 找到最终目标
        }
        
        // 继续解析符号引用
        refname = referent->buf;
    }
    return NULL;
}

引用验证与错误处理

Git实现了完善的引用验证机制:

// 引用名称安全性检查
int refname_is_safe(const char *refname)
{
    if (starts_with(refname, "refs/")) {
        // 检查是否包含非法字符或路径遍历
        return !strstr(refname, "/./") && 
               !strstr(refname, "/../") &&
               !strstr(refname, "//");
    } else {
        // 非refs/开头的特殊引用(如HEAD)
        return is_all_uppercase(refname);
    }
}

性能优化策略

Git在分支引用管理上采用了多种优化策略:

  1. 引用缓存:内存中维护引用状态缓存,减少文件IO
  2. 批量处理:使用事务机制批量处理多个引用更新
  3. 惰性加载:只有在需要时才解析引用内容
// 引用迭代器接口,支持高效遍历
struct ref_iterator *refs_ref_iterator_begin(struct ref_store *ref_store,
                                            const char *prefix,
                                            int trim, int flags)
{
    // 实现引用的高效遍历,支持前缀过滤和修剪
}

通过这种精心的设计,Git能够在保持强大功能的同时,确保分支引用和HEAD指针管理的高效性和可靠性。这种机制为开发者提供了灵活的分支操作体验,同时保证了版本控制数据的一致性和完整性。

标签对象与轻量级标签实现

在Git的引用系统中,标签(Tag)是用于标记特定提交的重要机制。Git支持两种类型的标签:注解标签(Annotated Tag)轻量级标签(Lightweight Tag)。这两种标签在内部实现和功能上有着本质的区别。

标签对象的结构定义

Git中的注解标签实际上是一种特殊的Git对象,其类型为OBJ_TAG(值为4)。在tag.h头文件中,标签对象的结构定义如下:

struct tag {
    struct object object;
    struct object *tagged;
    char *tag;
    timestamp_t date;
};

这个结构体包含以下关键字段:

  • object: 继承自基础对象结构,包含对象类型、标志和OID
  • tagged: 指向被标记的对象(提交、树、blob或其他标签)
  • tag: 标签名称字符串
  • date: 标签创建时间戳

注解标签的创建流程

注解标签的创建过程涉及多个步骤,主要通过build_tag_object()函数实现:

mermaid

标签对象的实际内容格式遵循特定的文本结构:

object <被标记对象的OID>
type <对象类型>
tag <标签名称>
tagger <作者信息> <时间戳>

<标签消息内容>

轻量级标签的实现机制

与注解标签不同,轻量级标签并不创建新的Git对象。它仅仅是一个指向特定提交的引用(reference),存储在.git/refs/tags/目录下。

轻量级标签的创建过程非常简单:

# 创建轻量级标签
git tag v1.0-lightweight

# 对应的引用文件内容
cat .git/refs/tags/v1.0-lightweight
# 输出: af5626b4a114abcb82d63db7c8082c3c4756e51b

核心函数解析

1. 标签查找与解析

lookup_tag()函数用于查找标签对象:

struct tag *lookup_tag(struct repository *r, const struct object_id *oid)
{
    struct object *obj = lookup_object(r, oid);
    if (!obj)
        return create_object(r, oid, alloc_tag_node(r));
    return object_as_type(obj, OBJ_TAG, 0);
}
2. 标签内容解析

parse_tag_buffer()函数负责解析标签对象的原始数据:

int parse_tag_buffer(struct repository *r, struct tag *item, 
                    const void *data, unsigned long size)
{
    // 解析object行
    if (memcmp("object ", bufptr, 7) || parse_oid_hex(bufptr + 7, &oid, &bufptr))
        return -1;
    
    // 解析type行
    if (!starts_with(bufptr, "type "))
        return -1;
    
    // 解析tag行
    if (bufptr + 4 < tail && starts_with(bufptr, "tag "))
        item->tag = xmemdupz(bufptr, nl - bufptr);
    
    // 解析tagger行(可选)
    if (bufptr + 7 < tail && starts_with(bufptr, "tagger "))
        item->date = parse_tag_date(bufptr, tail);
}
3. 标签解引用

deref_tag()函数用于递归解析标签链,直到找到非标签对象:

struct object *deref_tag(struct repository *r, struct object *o, 
                        const char *warn, int warnlen)
{
    while (o && o->type == OBJ_TAG) {
        if (((struct tag *)o)->tagged) {
            last_oid = &((struct tag *)o)->tagged->oid;
            o = parse_object(r, last_oid);
        } else {
            last_oid = NULL;
            o = NULL;
        }
    }
    return o;
}

两种标签的对比

下表详细对比了注解标签和轻量级标签的特性:

特性注解标签轻量级标签
存储方式Git对象(OBJ_TAG)引用文件
内容包含元数据和消息仅包含提交哈希
大小较大(包含完整信息)很小(仅40字节哈希值)
可签名是(支持GPG签名)
包含作者信息
包含时间戳
包含消息
查询性能需要对象解析直接引用访问

实际应用场景

注解标签的使用场景

注解标签适用于需要记录详细信息的正式发布版本:

# 创建带签名的注解标签
git tag -s v2.0.0 -m "正式发布版本2.0.0"

# 验证标签签名
git tag -v v2.0.0
轻量级标签的使用场景

轻量级标签适用于临时标记或内部使用的简单标记:

# 创建轻量级标签用于临时测试
git tag test-feature-branch

# 基于某个提交创建轻量级标签
git tag temp-mark af5626b

内部实现细节

标签对象的哈希计算

标签对象与其他Git对象一样,通过内容哈希来唯一标识:

// 在build_tag_object函数中
if (odb_write_object_ext(the_repository->objects, buf->buf,
                         buf->len, OBJ_TAG, result, compat_oid, 0) < 0)
    return error(_("unable to write tag file"));
标签引用的验证

Git提供了专门的函数来验证标签引用名称的合法性:

int check_tag_ref(struct strbuf *sb, const char *name)
{
    // 验证标签名称是否符合规范
    // 返回0表示有效,-1表示无效
}

性能考虑

在选择使用注解标签还是轻量级标签时,需要考虑以下性能因素:

  1. 存储开销:注解标签需要额外的存储空间,轻量级标签几乎无额外开销
  2. 传输效率:轻量级标签在推送和拉取时更高效
  3. 解析成本:注解标签需要解析对象内容,轻量级标签直接引用提交

通过深入理解Git标签的内部实现机制,开发者可以更加明智地选择适合自己需求的标签类型,并在版本控制过程中更好地利用标签功能。

引用事务与并发控制机制

Git的引用系统在分布式版本控制中扮演着核心角色,它通过精心设计的事务机制和并发控制策略,确保在多用户、多进程环境下引用操作的原子性和一致性。引用事务(Ref Transaction)是Git内部实现的关键抽象,它为分支创建、标签更新、HEAD移动等操作提供了可靠的并发安全保障。

引用事务的核心架构

Git的引用事务机制基于经典的ACID事务模型,通过struct ref_transaction数据结构来封装一组相关的引用操作。每个事务包含多个更新操作,这些操作要么全部成功提交,要么全部回滚,确保引用状态的一致性。

struct ref_transaction {
    struct ref_store *ref_store;
    struct string_list updates;
    struct strbuf err;
    unsigned int flags;
    enum ref_transaction_state state;
};

事务的生命周期遵循严格的状态机模型:

mermaid

事务操作类型与语义

Git引用事务支持四种基本操作类型,每种操作都有明确的语义约束:

操作类型函数描述前置条件
更新操作ref_transaction_update()修改现有引用的指向引用必须存在,旧值必须匹配
创建操作ref_transaction_create()创建新引用引用必须不存在
删除操作ref_transaction_delete()删除现有引用引用必须存在,旧值必须匹配
验证操作ref_transaction_verify()验证引用当前状态仅检查,不修改

每种操作都支持丰富的标志位来控制行为:

#define REF_TRANSACTION_UPDATE_ALLOWED_FLAGS \
    (REF_NO_DEREF | REF_FORCE_CREATE_REFLOG | \
     REF_SKIP_REFNAME_VERIFICATION | REF_SKIP_OID_VERIFICATION | \
     REF_SKIP_CONFLICT_CHECK)

并发控制策略

Git采用多层次的并发控制机制来应对不同的使用场景:

1. 文件锁机制(Files Backend)

对于传统的文件存储后端,Git使用.lock文件来实现细粒度的锁控制:

static int files_copy_or_rename_ref(struct ref_store *ref_store,
                   const char *oldrefname, const char *newrefname,
                   int copy, int log, struct strbuf *err)
{
    struct files_ref_store *refs = files_downcast(ref_store);
    struct strbuf oldpath = STRBUF_INIT;
    struct strbuf newpath = STRBUF_INIT;
    struct lock_file oldlock = LOCK_INIT;
    struct lock_file newlock = LOCK_INIT;
    int ret;
    
    // 获取旧引用和新引用的锁
    if (hold_lock_file_for_update(&oldlock, oldpath.buf, LOCK_NO_DEREF) < 0 ||
        hold_lock_file_for_update(&newlock, newpath.buf, LOCK_NO_DEREF) < 0) {
        ret = -1;
        goto out;
    }
    // 执行复制或重命名操作
    // ...
out:
    rollback_lock_file(&oldlock);
    rollback_lock_file(&newlock);
    strbuf_release(&oldpath);
    strbuf_release(&newpath);
    return ret;
}
2. 事务级隔离

引用事务提供可序列化(Serializable)的隔离级别,通过两阶段提交协议实现:

准备阶段(Prepare Phase)

  • 验证所有操作的可行性
  • 检查引用名称冲突
  • 验证旧值一致性
  • 预留必要的资源

提交阶段(Commit Phase)

  • 原子性地应用所有更改
  • 更新引用日志(reflog)
  • 释放锁资源
int ref_transaction_prepare(struct ref_transaction *transaction,
               struct strbuf *err)
{
    struct ref_store *refs = transaction->ref_store;
    int ret;
    
    if (transaction->state != REF_TRANSACTION_UPDATES_ADDED)
        BUG("transaction in wrong state");
    
    ret = refs->be->transaction_prepare(refs, transaction, err);
    if (!ret)
        transaction->state = REF_TRANSACTION_PREPARED;
    
    return ret;
}

错误处理与恢复机制

Git引用事务定义了丰富的错误代码来处理各种异常情况:

enum ref_transaction_error {
    REF_TRANSACTION_ERROR_GENERIC = -1,
    REF_TRANSACTION_ERROR_NAME_CONFLICT = -2,
    REF_TRANSACTION_ERROR_CREATE_EXISTS = -3,
    REF_TRANSACTION_ERROR_NONEXISTENT_REF = -4,
    REF_TRANSACTION_ERROR_INCORRECT_OLD_VALUE = -5,
    REF_TRANSACTION_ERROR_INVALID_NEW_VALUE = -6,
    REF_TRANSACTION_ERROR_EXPECTED_SYMREF = -7,
};

每个错误类型都有对应的恢复策略:

错误类型描述恢复策略
NAME_CONFLICT引用名称冲突重命名或删除冲突引用
CREATE_EXISTS创建已存在的引用使用更新操作或强制标志
NONEXISTENT_REF引用不存在检查引用路径或使用创建操作
INCORRECT_OLD_VALUE旧值不匹配获取最新值重试操作

性能优化策略

为了在高并发场景下保持性能,Git实现了多种优化机制:

1. 批量处理优化

事务机制允许将多个引用操作批量处理,减少锁竞争和IO操作:

// 批量更新多个引用
struct ref_transaction *transaction = ref_store_transaction_begin(refs, 0, &err);
ref_transaction_update(transaction, "refs/heads/main", &new_oid, &old_oid, 0, NULL, &err);
ref_transaction_update(transaction, "refs/heads/develop", &new_oid2, &old_oid2, 0, NULL, &err);
ref_transaction_commit(transaction, &err);
2. 锁粒度优化

Git根据操作类型动态调整锁粒度:

  • 细粒度锁:单个引用操作使用引用级锁
  • 中粒度锁:事务操作使用事务级锁
  • 粗粒度锁:仓库级操作使用全局锁
3. 冲突检测优化

通过提前验证和冲突预测,减少不必要的锁等待:

enum ref_transaction_error refs_verify_refname_available(
    struct ref_store *refs,
    const char *refname,
    const struct string_list *extras,
    const struct string_list *skip,
    struct strbuf *err)
{
    // 检查引用名称是否可用
    // 提前检测潜在冲突
}

实际应用场景

引用事务机制在Git的多个核心功能中发挥关键作用:

1. 分支操作
// 创建新分支的事务实现
int create_branch_transaction(struct repository *r, const char *name, 
                             const struct object_id *oid)
{
    struct strbuf refname = STRBUF_INIT;
    struct strbuf err = STRBUF_INIT;
    struct ref_transaction *transaction;
    int ret;
    
    strbuf_addf(&refname, "refs/heads/%s", name);
    transaction = ref_store_transaction_begin(r->refs, 0, &err);
    
    ret = ref_transaction_create(transaction, refname.buf, oid, NULL, 0, NULL, &err);
    if (!ret)
        ret = ref_transaction_commit(transaction, &err);
    
    ref_transaction_free(transaction);
    strbuf_release(&refname);
    return ret;
}
2. 标签管理
// 标签创建与验证
int create_annotated_tag(struct repository *r, const char *name,
                        const struct object_id *oid, const char *message)
{
    struct ref_transaction *transaction;
    struct strbuf refname = STRBUF_INIT;
    struct strbuf err = STRBUF_INIT;
    int ret;
    
    strbuf_addf(&refname, "refs/tags/%s", name);
    transaction = ref_store_transaction_begin(r->refs, 0, &err);
    
    // 验证标签不存在
    ret = ref_transaction_verify(transaction, refname.buf, NULL, NULL, 0, &err);
    if (!ret)
        ret = ref_transaction_create(transaction, refname.buf, oid, NULL, 0, NULL, &err);
    if (!ret)
        ret = ref_transaction_commit(transaction, &err);
    
    ref_transaction_free(transaction);
    strbuf_release(&refname);
    return ret;
}
3. HEAD引用更新

HEAD引用的更新需要特殊处理,确保工作树和索引的一致性:

int update_head_transaction(struct repository *r, const char *refname,
                          const struct object_id *oid, const char *message)
{
    struct ref_transaction *transaction;
    struct strbuf err = STRBUF_INIT;
    int ret;
    
    transaction = ref_store_transaction_begin(r->refs, 0, &err);
    ret = ref_transaction_update(transaction, "HEAD", oid, NULL, 
                                REF_NO_DEREF, message, &err);
    if (!ret)
        ret = ref_transaction_commit(transaction, &err);
    
    ref_transaction_free(transaction);
    return ret;
}

Git的引用事务与并发控制机制通过精心的设计和多层次的策略,为分布式版本控制系统提供了强大而可靠的基础设施。这种机制不仅保证了数据的一致性,还在高并发环境下保持了良好的性能表现,是Git能够成为业界标准版本控制系统的重要技术基础。

总结

Git引用系统通过精心的架构设计和多层次的控制策略,为分布式版本控制提供了强大而可靠的基础设施。从引用存储的物理结构到命名空间设计,从分支引用管理到标签对象实现,再到引用事务与并发控制机制,每个组件都体现了Git对数据一致性和性能优化的深度考量。这种机制不仅保证了版本控制数据的一致性,还在高并发环境下保持了良好的性能表现,是Git能够成为业界标准版本控制系统的重要技术基础。通过深入理解这些内部工作原理,开发者可以更加有效地利用Git的强大功能,提升版本控制管理的效率和可靠性。

【免费下载链接】git Git Source Code Mirror - This is a publish-only repository but pull requests can be turned into patches to the mailing list via GitGitGadget (https://gitgitgadget.github.io/). Please follow Documentation/SubmittingPatches procedure for any of your improvements. 【免费下载链接】git 项目地址: https://gitcode.com/GitHub_Trending/gi/git

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值