Sapling IndexedLog 存储引擎深度解析:为版本控制系统而生的可索引追加式日志
开发工具CLI后端【免费下载链接】saplingA Scalable, User-Friendly Source Control System.项目地址https://gitcode.com/gh_mirrors/sa/sapling点击查看免费下载IndexedLog 是 Sapling 的核心磁盘存储格式被 dag有向无环图、revisionstore修订存储等大量组件使用。它以Log追加式主存储 多 Index纯函数驱动索引的架构同时实现了 O(log N) 哈希查找、无拓扑序约束的按哈希插入、无锁读取与数据完整性校验。本文基于官方内部文档 website/docs/dev/internals/indexedlog.md 并结合 eden/scm/lib/indexedlog 源码实现完整解析其设计动机、磁盘格式、并发模型、修复机制与实际应用帮助你理解 Sapling 底层数据如何被组织、加速与自愈。设计背景为什么不能沿用 Revlog、Git 与 SQLiteIndexedLog 并非凭空发明而是在评估了三类既有存储方案Mercurial 的 Revlog、Git 的 loose/pack 文件、SQLite的局限性之后专门设计的产物。理解这些局限是理解 IndexedLog 每一项特性取舍的前提。Revlog 的三大限制历史上Sapling 的前身使用 revlog 作为主要存储格式但它存在三个结构性缺陷修订必须满足拓扑序Revlog 只允许追加且要求追加的修订必须是已有修订的祖先。当按需拉取修订时如果先取到了较新的修订就无法再追加作为其祖先的较老修订导致数据无法落盘。按哈希查找可能退化为线性扫描当修订数量很大时通过 hash 定位一条记录需要线性遍历性能明显劣化。虽然可以用额外构建的索引绕开但那是补丁式的权宜之计。通用性不足Revlog 中一条记录只能有一个 SHA1 作为 key。但像 obsstore废除修订存储这样的场景需要同时用前驱predecessor和后继successor多种索引去查找记录且一条记录可能对应多个前驱或后继Revlog 无法表达。Git loose / pack 文件的问题Git 的格式loose 对象与 pack 文件没有拓扑序限制理想情况下按哈希查找是 O(log N)。但它有两个不足依赖周期性的repack才能维持查找性能pack 文件过多时性能会下降属于需要人工/定时维护的方案。同样不支持多索引、多 key 的通用场景。SQLite 的问题SQLite 功能强大天然支持多索引且无需 repack 即可维持时间复杂度。但它的核心问题在于锁模型Sapling 历史上采用只追加append-only策略来达成无锁读取而 SQLite 无论读还是写都需要加锁与这一策略冲突。结论IndexedLog 就是为同时满足无锁读 O(log N) 查找 无拓扑序追加 多索引通用性这四个目标而生的自有格式。设计目标一览IndexedLog 明确追求以下核心属性源自 indexedlog.mdO(log N) 查找且不需要repack来维持性能按哈希插入不受拓扑序限制无锁读取通过较大的文件只追加 较小的文件原子替换来实现依赖现代文件系统 API事务性文件系统或块级写时复制理论上更好但普遍不可用通用性支持多索引、每条记录多 key数据完整性锦上添花在硬重启或误操作如对仓库数据执行sed -i后能精确定位损坏的数据范围并提供恢复手段。整体架构Log 与多个 Index一个 IndexedLog 由一个 Log和多个环绕它的 Index组成源码中由Log结构体持有indexes: VecIndex体现Log 是数据的唯一事实来源single source of truthIndex 完全由 Log 与索引函数派生而来——只要 Log 完好损坏的 Index 可以删除后重建。在 lib.rs 的模块说明中官方将其概括为an integrity-checked, append-only storage with index support带索引支持、带完整性校验的追加式存储并建议先看log::Log索引可独立使用index::Index。Log追加式的主存储语义模型Log 按插入顺序保存条目每条目是一个字节切片。接口语义上类似于LinkedListVecu8但只允许push_back不允许push_front。支持的操作只有两个按插入顺序迭代所有条目在末尾追加一条新条目。明确不支持的操作按偏移量随机读取某条随机访问删除某条记录。与关系型数据库或文档数据库不同Log 自身不定义条目内字节的含义字节的语义完全由上层应用决定。官方文档原话是The meaning is up to the application to decide.磁盘布局源码级从 log.rs 的格式注释可以看到Log 在磁盘上由一个目录构成包含三类文件主日志文件log追加式文件每条记录格式为ENTRY_FLAGS LEN(CONTENT) CHECKSUM CONTENT即标志位 内容长度 校验和 内容校验和可以是空的、XXHASH64 或 XXHASH32多个用户自定义索引文件每个索引使用追加式的 radix-tree 磁盘表示外加一个小的、非追加式的校验和文件一个小的元数据文件meta记录 log 与 index 文件的逻辑长度逻辑长度用于识别哪些尾部字节属于未完整刷新的垃圾数据。文件命名常量可参见 log.rs主文件名为log头部魔数为indexedlog0\012 字节因此有效数据从偏移 12 开始元数据文件名为meta。索引文件前缀为index2-元数据前缀为2-见 open_options.rs。日志本身的写耗时会记录到indexedlog.log.write_ms计数器sync次数记录到indexedlog.sync见 log.rs这些指标可供上层观测磁盘写入压力。Index纯函数驱动的多索引一个 Log 可以挂多个索引。每个索引由两部分组成索引名name索引函数index function一个接收条目字节切片、输出一组IndexOutput的纯函数。IndexOutput 的四种指令IndexOutput是一个枚举用于指示 IndexedLog 执行以下操作之一定义见 open_options.rs变体语义适用场景Reference(Rangeu64)索引 key 是输入条目内的一段字节切片相对引用优先使用生成的索引更小Owned(Box[u8])索引 key 是与输入无关的一段独立字节key 不在条目内如条目被压缩时使用Remove(Box[u8])在索引中移除该 key 关联的所有值删除操作只影响索引条目仍留在 Log 中RemovePrefix(Box[u8])在索引中移除所有以该前缀开头的 key批量删除每条记录可以产生零个或多个key。文档中给出了一个直观例子若 Log 存的是 Git 提交索引用于根据父提交哈希找子提交则索引函数解析提交元数据后把 0、1、2 个甚至更多父提交哈希都输出为 key。索引函数必须是纯 Rust 函数与数据库不同索引函数是原生 Rust 代码而非 SQL 或 JSON 这类可序列化的独立语言。这意味着编译后的 Rust 函数无法序列化到磁盘因此每次从磁盘加载 IndexedLog 时应用必须提供完全相同的索引函数索引函数应当纯净且快速——不应依赖网络、文件系统或外部随机源见 open_options.rs。索引命名约定修改索引函数时必须同时修改索引名否则 IndexedLog 可能错误地复用旧的索引数据。源码中对索引名还有两条硬性约束见 open_options.rs名字会作为索引文件名的组成部分因此不能用用户生成的内容也不能包含..或/这类路径穿越字符。Standalone Index可独立使用的索引Index 可以不依赖 Log 单独使用。其接口语义类似于BTreeMapVecu8, LinkedListu64但主存储是文件系统而非内存。支持的操作插入 (key, value)value 被插入到该 key 对应链表的前端也可以直接丢弃现有链表按范围查找 key范围查询按范围删除 key范围删除。内部结构radix tree 单向链表key 部分是一棵radix tree基数树每个节点有16 个孩子4 bit专门为了支持 hex十六进制前缀查找。源码 index.rs 说明RADIX 条目有 16 个孩子主要服务于源码控制场景的十六进制哈希缺失的孩子在跳转表中对应偏移为 0。value 部分是单向链表支持push_front不支持push_back。无锁读取持久化数据结构Index 的磁盘格式采用**持久化数据结构persistent data structure**来实现无锁读取主索引文件只追加指向根树节点的指针是一小块独立数据通过**原子替换atomic-replace**更新。与 Log 配合时的 lag滞后机制当与 Log 一起使用时LinkedListu64中的u64就是文件偏移。这些偏移不出现在 Log 的公共 API 中以防止误用。由于为单条记录更新索引需要 O(log N) 的磁盘空间频繁的小写入很不划算因此Log 允许磁盘上的索引对部分条目滞后滞后部分的索引会在内存中按需构建Log::open时补齐见 open_options.rs 对lag_threshold的说明。这正是IndexDef中lag_threshold字段的用途——它表示磁盘上允许未被索引的字节数实际效果与该索引函数的执行速度正相关。并发写入模型快照语义与 sync 锁IndexedLog 的并发模型可以概括为读快照、写缓冲、sync 落盘加载即快照当 IndexedLog或独立的 Index从磁盘加载时相当于拍了一张快照。之后磁盘上的变化不会影响已加载的实例前提是所有文件写入都经由 IndexedLog 的 API。写入先在内存缓冲无锁内存中的写入对其他进程、以及其他已加载的 IndexedLog 实例完全不可见。sync才触碰磁盘sync操作会先获取文件系统锁阻止其他写入者然后拾取磁盘上最新的文件系统状态若有变化把更新的 Log 和索引写到磁盘最后释放锁。两个进程或同一进程内的两个 IndexedLog 实例并发sync()同一个磁盘 IndexedLog 时双方的待写入内容都会落盘但写入顺序不确定取决于谁先拿到文件系统锁。这是文档明确说明的行为边界。代码层面Log在 flush 时会通过ScopedDirLock对目录加flock见 log.rs 的注释Flushing in-memory parts to disk requires taking a flock on the directory并借助基于 mmap 的跨进程廉价变更检测器SharedChangeDetector判断磁盘是否真的变了见 log.rs。数据完整性xxhash 校验与 repair 修复校验策略Log 与 Index 都使用 xxhash 保证数据完整性但粒度不同Log按条目计算校验和根据条目大小自动选择 XXH32 或 XXH64对应ChecksumType::Auto也可显式指定Xxhash64或Xxhash32见 open_options.rs其中 XXH64 在 64 位平台更快、占用空间更大XXH32 更省空间、适合短条目Index内部每 1MB 数据维护一条校验和源码常量INDEX_CHECKSUM_CHUNK_SIZE_LOGARITHM: u32 20即 2^20 字节见 log.rs。所有数据读取都会触发完整性校验错误会报告给应用层。此外Index 旁边会生成一个index.verified缓存文件魔数ilogvrf1记录当前操作系统启动后哪些校验块已被验证过使后续进程可以跳过重复哈希每验证 16 个新块即保存一次缓存以降低突然退出时的损失见 index.rs。repair 修复流程IndexedLog 支持repair修复操作将 Log截断到通过完整性校验的条目为止丢弃尾部损坏数据重建损坏或过期的索引因为索引可由 Log 索引函数重新派生。repair的语义在源码 repair.rs 中有详细定义Repairtrait 的repair(path)负责修复指定路径的结构可递归OpenWithRepair::open_with_repair约定先 open若遇到数据损坏错误则修复一次再 open。两点重要限制它只修复打开过程中发现的一类损坏通常由 OS 崩溃或硬重启导致不修复 open 之后读取数据时可能发生的损坏出于性能考虑它不会全量验证所有数据如果还有其他读者repair 会被跳过——因为 IndexedLog 依赖追加式保证无锁读取而 repair 是非追加的破坏性写入可能让正在读取的其他进程静默拿到错误数据。修复过程会向目录下的repair.log追加诊断信息超过 1MB 时自动截断重写见 repair.rs。RotateLog有界的客户端缓存RotateLog 把**日志轮转log rotation**的思想应用到 IndexedLog 上源码 rotate.rs维护一个Log 列表当某个 Log 超过大小上限时创建新 Log并可选地删除最老的 Log磁盘上表现为一个目录内含0/、1/、2/...每个 Log 一个子目录外加一个latest文件记录当前活跃目录见 rotate.rs。RotateLog 的定位是客户端缓存客户端希望磁盘占用有界且数据可从服务器重新拉取。其默认配置见 rotate.rs为保留 2 个 Logmax_log_count 2值越大越伤查找性能单个 Log 达到 2GB 触发轮转max_bytes_per_log 2_000_000_000无索引、不自动创建、追加后不自动 sync。RotateLog 的OpenOptions还支持auto_sync_threshold内存缓冲超过阈值自动 sync约束内存占用、flush_filtersync 时过滤/改写内容例如避免写回最新 Log 中已存在的内容、btrfs_compressionbtrfs 透明压缩下按物理大小而非表观大小判断轮转等选项见 rotate.rs。在 Sapling 中的实际应用IndexedLog 不是纸面设计而是 Sapling 大量底层组件的存储地基。以下是仓库内可验证的两大应用场景。1. DAG修订图存储Sapling 的 DAG 层直接构建在 IndexedLog 之上。indexedlog_dag.rs 中Dag::default_open_options通过multi::OpenOptions::from_name_opts声明了两个命名 Logidmap2IdMapid ↔ 顶点哈希映射的日志iddagIdDagid 级有向无环图的索引化日志存储。打开 DAG 时调用open_with_repair获得自动修复能力indexedlog_dag.rs写入则通过MultiLog的锁 write_meta完成元数据持久化。这里同时用到了本篇文章讲过的多 Log、索引、repair 等全部机制——DAG 的高效祖先查询O(log N)正是靠这些索引支撑的。2. RevisionStore修订数据存储revisionstore模块是 IndexedLog 的另一个重要用户indexedlogdatastore.rs以 IndexedLog 存储修订数据内容indexedloghistorystore.rs以 IndexedLog 存储修订历史hg 风格的父/子关系索引天然是多 key 场景。这两处恰好印证了文档所说的多索引、多 key 通用性价值一条修订记录的父提交、子提交可以同时被多个索引覆盖而无需像 Revlog 那样只能有一个 SHA1 key。延伸阅读与 IndexedLog 紧密相关的还有 metalog.md元数据日志同样基于 IndexedLog/追加式存储思想与 zstdelta.md压缩增量格式常与 IndexedLog 配合存储大数据。总结IndexedLog 用一组极其克制的原语只追加的 Log 纯函数派生的多 Index 原子替换小文件 文件锁 sync换来了版本控制场景下最想要的三件事无锁读取读进程永不等待写进程无拓扑约束的按哈希插入服务端数据可按需、乱序地落盘O(log N) 查找且免 repack借助 radix tree 与持久化数据结构无需周期性维护即可保持性能稳定。在此基础上xxhash 分块校验与 repair 机制让它在面对硬重启、磁盘位翻转甚至误sed -i时具备可定位、可自愈的能力。理解 IndexedLog等于同时理解了 Sapling 的数据存储哲学——它并不追求做一个通用的数据库而是把追加式 索引 完整性这套组合拳精准地打在版本控制工作负载最痛的地方。赞分享开发工具CLI后端【免费下载链接】saplingA Scalable, User-Friendly Source Control System.项目地址https://gitcode.com/gh_mirrors/sa/sapling点击查看免费下载相关推荐Apache Doris存储引擎深度剖析列式存储与索引优化Apache Doris存储引擎深度剖析列式存储与索引优化 本文深入解析Apache Doris存储引擎的核心技术重点剖析列式存储架构的优势与实现原理详细数据库OLAP大数据数据仓库分布式数据库实时分析列式数据库存储与检索从日志结构到 B 树、列式存储与向量索引的存储引擎全景解析DDIA 第四章存储与检索从日志结构到 B 树、列式存储与向量索引的存储引擎全景解析DDIA 第四章 本文基于 content/zh/ch4.md https://lin文档教程OpenTrack性能优化终极指南多GPU并行训练与JAX加速技巧OpenTrack性能优化终极指南多GPU并行训练与JAX加速技巧 OpenTrack作为Any2Track的官方实现是一个专注于机器人运动跟踪的开源项目。上一篇LeetCode 1055 Shortest Way to Form String 四种解法全解析子序列判定、双指针、倒排索引与 2D 下一出现位置表下一篇深入理解 Terraform AWS Provider 的 aws_iam_saml_provider 数据源读取 IAM SAML 联合身份元数据创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

UNet车道线分割实战:Tusimple数据集端到端训练与TensorRT加速

UNet车道线分割实战:Tusimple数据集端到端训练与TensorRT加速

简介:本资源是一份基于U-Net架构在TuSimple数据集上实现车道线检测的完整PyTorch实践方案,面向计算机视觉初学者、自动驾驶方向学习者及图像分割任务实践者,聚焦小目标边界分割这一典型难点问题。压缩包共15个文件,含7个核心Pytho…

2026/10/9 12:33:59 阅读更多 →
喷码OCR缺陷检测实战:从数据标注到模型训练与VisualDL分析

喷码OCR缺陷检测实战:从数据标注到模型训练与VisualDL分析

简介:面向工业自动化的缺陷检测实战项目,专注于OCR喷码缺陷检测,适合机器视觉入门者及有经验的工程师。资源围绕喷码字符识别与缺陷判定,涵盖数据收集、图像预处理、特征提取、模型训练到检测算法实现的完整流程,并提供…

2026/10/9 12:33:59 阅读更多 →
恶意代码检测图像化平台:字节转灰度图与CNN分类

恶意代码检测图像化平台:字节转灰度图与CNN分类

简介:这是一个面向计算机相关专业本科生的毕业设计完整项目,主题为恶意代码检测分类平台,融合程序设计、算法模型与Web管理端展示。资源定位清晰,适合需要毕设参考、课设拓展或对恶意样本分类感兴趣的开发者。包内共157个文件&…

2026/10/9 12:33:59 阅读更多 →

最新新闻

YOLO实时物体检测实战:从齿条螺栓螺母裂纹数据集到TensorRT部署

YOLO实时物体检测实战:从齿条螺栓螺母裂纹数据集到TensorRT部署

简介:面向工业质检与计算机视觉开发者的YOLO实时物体检测工程包,聚焦齿条、螺栓、螺母及裂缝等目标的识别与定位,适合有深度学习基础的开发者进行算法研究或项目移植;YOLO本身将检测任务转化为单个回归问题,通过网格与…

2026/10/10 16:02:54 阅读更多 →
用C#与easyHook实现Win32 API Hook:程序行为监控与远程注入实战

用C#与easyHook实现Win32 API Hook:程序行为监控与远程注入实战

简介:这是一份C# EasyHook库的完整使用示例工程,面向需要在运行时实现跨进程函数拦截与注入的.NET开发者,适合对Windows钩子机制有一定了解、希望快速上手EasyHook的读者。包内包含WinForms测试窗口、类库工程与可运行Demo,覆盖了…

2026/10/10 16:02:54 阅读更多 →
yolov5果蔬识别实战:数据集构建、训练调参与产线部署避坑指南

yolov5果蔬识别实战:数据集构建、训练调参与产线部署避坑指南

简介:这是一套面向深度学习入门者与计算机视觉方向学生的YOLOv5果蔬识别完整项目包,围绕土豆、圣女果、大白菜、大葱、梨、胡萝卜、芒果、苹果、西红柿、韭菜、香蕉、黄瓜等十余类常见果蔬的检测任务展开,可用于课程设计、毕业设计或算法练手…

2026/10/10 16:02:54 阅读更多 →
O2O平台CRM系统架构设计:从线索公私海到平台化落地

O2O平台CRM系统架构设计:从线索公私海到平台化落地

简介:美团O2O的CRM系统架构设计.doc 以美团 CRM 为样本,系统拆解 O2O 平台如何借助客户关系管理增强线下资源控制与服务品质。资源面向产品经理、B端运营及电商架构师,适合需要理解销售线索管理、运营中台、数据决策支持和移动办公场景的读者…

2026/10/10 16:02:54 阅读更多 →
ElasticSearch搜索系统建设实战:从Docker部署到线上自愈

ElasticSearch搜索系统建设实战:从Docker部署到线上自愈

简介:本资源是一份面向Java开发者与技术分享者的ElasticSearch入门到进阶PPT课件,共40余页,系统梳理了搜索引擎选型必要性、Lucene演进脉络、ES核心架构(节点/集群/分片/副本)、RESTful API实践要点及与Solr、Splunk的…

2026/10/10 16:02:54 阅读更多 →
热电联产机组联合优化调度:Matlab+YALMIP建模风电消纳与储热电锅炉算例

热电联产机组联合优化调度:Matlab+YALMIP建模风电消纳与储热电锅炉算例

1. 冬季供暖季的弃风困局:热电联产机组到底卡在哪每年供暖季一过,风电场的同事就开始盯着调度曲线叹气:白天风光还好,一到后半夜风速上来了,风电场却得压出力,甚至有整场停机的时候。而另一边,热…

2026/10/10 16:01:52 阅读更多 →

日新闻

卫星轨道分类全解析:从LEO到GEO的选型逻辑与工程实践

卫星轨道分类全解析:从LEO到GEO的选型逻辑与工程实践

1. 从“卫星轨道分类”这个标题说起:为什么值得花时间搞懂第一次接触“卫星轨道分类”这个概念,很多人会觉得它离自己很远——不就是天上的星星怎么转吗?但如果你正在做航天任务规划、遥感数据接收、星座设计,甚至只是准备一场航天…

2026/10/10 0:00:39 阅读更多 →
Spring AOP 核心原理与实战:从概念到日志切面落地

Spring AOP 核心原理与实战:从概念到日志切面落地

1. 从一个真实痛点说起:为什么你的代码里到处都是重复逻辑刚入行那会儿,我写过一个用户管理模块,注册、登录、改密码、注销四个接口。每个接口里都塞了几乎一样的日志打印、参数校验、事务开启和提交。当时觉得没什么,能跑就行。直…

2026/10/10 0:00:40 阅读更多 →
Python招聘数据采集与分析可视化:从采集清洗到薪资技能城市可视化全链路

Python招聘数据采集与分析可视化:从采集清洗到薪资技能城市可视化全链路

简介:这是一套面向计算机相关专业学生与项目实战学习者的Python数据采集与分析可视化完整项目,以Boss直聘岗位数据为对象,适合用作毕业设计、课程设计或期末大作业。资源包共38个文件,约246KB,以13个py源码文件为核心&…

2026/10/10 0:00:40 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 11:14:25 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 1:36:08 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 11:14:58 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 5:23:50 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 21:32:20 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 10:38:42 阅读更多 →