Codex 系统手册

补丁应用引擎和补丁执行适配器

stage-14.2.39 个文件

这一阶段发生在系统干活时,专门管“让模型改文件”这件事,像一套带安检的维修通道。apply_patch_spec 先把补丁格式说明白;前台 handler 和 core/apply_patch 检查格式、权限,必要时请用户确认,再交给运行环境。runtime 负责审批后在沙箱里动手。parser 把补丁拆成改动清单,streaming_parser 边收边拆;invocation 先认出调用并预查路径和文件;lib 真正落盘;git-utils 借 git 试跑或应用,并整理冲突结果。

本阶段的文件9

工具接口与编排

这些文件定义外部 apply_patch 工具接口,并编排请求从自由格式输入到验证、委派和进度报告的全过程。

core/src/tools/handlers/apply_patch_spec.rs源码 ↗
configtool setup / request preparation

这个文件像是在给模型发一张“工具使用说明书”。apply_patch 是一个自由格式工具,也就是模型调用它时不用包成 JSON,而是直接写一段补丁文本。为了让这段文本有规矩,文件会读取旁边的 apply_patch.lark 语法文件;Lark 是一种写语法规则的格式,可以理解成“这份补丁必须长成什么样”的说明。create_apply_patch_freeform_tool 会把工具名字、说明文字、语法类型和完整语法规则打包成 ToolSpec,交给上层系统使用。它还有一个开关 include_environment_id:如果打开,就允许补丁开头额外带上环境 ID,用来区分补丁要应用到哪个运行环境。没有这个文件,上层就不知道 apply_patch 工具的名字、用途和可接受格式,模型生成的文件修改也更容易失控或无法识别。

函数细节1
create_apply_patch_freeform_tool9–27 ↗
fn create_apply_patch_freeform_tool(include_environment_id: bool) -> ToolSpec

作用:这个函数创建 apply_patch 工具的规格说明,告诉系统和模型:这个工具叫什么、怎么描述、补丁文本必须符合什么语法。调用者会用它来把“修改文件”这个能力加入可用工具列表。

数据流:进去的是一个布尔值 include_environment_id,表示补丁格式里要不要允许写环境 ID。函数先读取内置的补丁语法文本;如果需要环境 ID,就把语法里的起始规则替换成带 environment_id 可选字段的版本;如果不需要,就原样使用语法。最后它把工具名、说明、语法类型和语法内容装进 FreeformTool,再包成 ToolSpec::Freeform 返回;它不直接改文件,只产出一份工具说明。

调用关系:在整体流程里,上层的 spec 会调用它来生成 apply_patch 的工具定义。它自己把最终结果交给 ToolSpec::Freeform 这个构造入口包装起来,之后真正的工具调用和补丁执行会由别的模块根据这份定义继续处理。

调用图:被 1 处调用(spec);外部调用 1 个(Freeform)。

core/src/tools/handlers/apply_patch.rs源码 ↗
orchestrationrequest handling

apply_patch 可以理解成“按补丁改文件”的工具。这个文件做的不是亲手改每一行,而是把整个流程串起来:先读取模型传来的补丁文本,解析并验证;再确定要在哪个环境里改;然后根据沙箱权限(沙箱就是限制程序能读写哪里的安全围栏)算出是否需要额外批准;最后要么直接返回结果,要么交给 ApplyPatchRuntime 真正执行。它还会在补丁还没完全传完时,边读边发“将要改哪些文件”的进度事件,并且用 500 毫秒缓冲,避免消息刷屏。文件里另一个重要入口是 intercept_apply_patch,它能拦截普通命令里的 apply_patch,把它转成同一套安全流程。

函数细节24
ApplyPatchHandler::new66–68 ↗
fn new(multi_environment: bool) -> Self

作用:创建一个 apply_patch 工具处理器,并记住这次是否允许用户指定不同的运行环境。有人注册核心工具时会用它。

数据流:进去一个布尔值 multi_environment,表示能不能选环境 → 它把这个值放进 ApplyPatchHandler 结构里 → 出来一个新的处理器对象,后面每次调用 apply_patch 都会参考这个开关。

调用关系:它由 add_core_utility_tools 在注册工具时调用,之后这个处理器会被工具系统拿来提供工具说明、接收调用和执行补丁。

调用图:被 1 处调用(add_core_utility_tools)。

ApplyPatchArgumentDiffConsumer::consume_diff79–91 ↗
fn consume_diff(
        &mut self,
        turn: &TurnContext,
        call_id: String,
        diff: &str,
    ) -> Option<EventMsg>

作用:在模型一点点输出补丁参数时,尝试把新来的片段转成“补丁进度更新”事件。这样界面可以提前看到哪些文件可能会被改。

数据流:进去当前回合、工具调用 ID、刚新增的一段补丁文字 → 它先看功能开关 ApplyPatchStreamingEvents 是否打开,没开就什么也不发;开了就把文字交给 push_delta 解析 → 出来可能是一个 PatchApplyUpdated 事件,也可能是空。

调用关系:它是 ToolArgumentDiffConsumer 接口的一部分,由工具框架在参数流式到达时调用;真正解析和限速发送的工作交给 ApplyPatchArgumentDiffConsumer::push_delta。

调用图:调用 1 个内部函数(push_delta)。

ApplyPatchArgumentDiffConsumer::finish93–96 ↗
fn finish(&mut self) -> Result<Option<EventMsg>, FunctionCallError>

作用:在补丁参数全部传完时收尾,确保最后一条被缓冲的进度更新没有丢掉。

数据流:进去它自己保存的解析器状态和待发送事件 → 它调用 finish_update_on_complete 完成解析并取出最后缓存的事件 → 出来可能是一条最终的 PatchApplyUpdated 事件,或者是解析失败错误。

调用关系:它由工具框架在参数流结束时调用;收尾细节交给 ApplyPatchArgumentDiffConsumer::finish_update_on_complete。

调用图:调用 1 个内部函数(finish_update_on_complete)。

ApplyPatchArgumentDiffConsumer::push_delta100–121 ↗
fn push_delta(&mut self, call_id: String, delta: &str) -> Option<PatchApplyUpdatedEvent>

作用:把刚收到的一小段补丁文本喂给流式解析器,并在合适的时候生成进度事件。它还会做节流,避免短时间内发太多更新。

数据流:进去调用 ID 和一段新增补丁文字 → 它让 StreamingPatchParser 继续解析,得到新识别出的 hunk(补丁里的一个改动块);再把这些改动块转成协议里的文件变化格式;如果距离上次发送不到 500 毫秒,就先缓存起来,否则立刻发出 → 出来可能是一条 PatchApplyUpdatedEvent,也可能暂时没有输出,但内部会更新缓存和发送时间。

调用关系:它被 consume_diff 调用;它依赖 convert_apply_patch_hunks_to_protocol 把底层补丁块翻译成前端和协议能看懂的 FileChange。

调用图:调用 2 个内部函数(push_delta, convert_apply_patch_hunks_to_protocol);被 1 处调用(consume_diff);外部调用 1 个(now)。

ApplyPatchArgumentDiffConsumer::finish_update_on_complete123–135 ↗
fn finish_update_on_complete(
        &mut self,
    ) -> Result<Option<PatchApplyUpdatedEvent>, FunctionCallError>

作用:结束流式补丁解析,并把之前因为限速暂存的最后一次进度更新补发出去。

数据流:进去当前流式解析器和 pending 缓存 → 它要求解析器 finish,确认整个补丁到结尾仍然合法;如果解析失败就生成给模型看的错误;如果有缓存事件就取出来并更新时间戳 → 出来是最后一条进度事件或空。

调用关系:它被 finish 调用,是流式参数收尾阶段的最后一道检查,保证进度事件既不太频繁,也不遗漏最后变化。

调用图:调用 1 个内部函数(finish);被 1 处调用(finish);外部调用 1 个(now)。

convert_apply_patch_hunks_to_protocol138–160 ↗
fn convert_apply_patch_hunks_to_protocol(hunks: &[Hunk]) -> HashMap<PathBuf, FileChange>

作用:把补丁解析器内部的改动块,转换成系统协议里统一使用的文件变化格式。这样 UI、事件系统和后续流程不用懂底层补丁细节。

数据流:进去一组 Hunk:可能是新增文件、删除文件、更新文件 → 它逐个取出文件路径,并按类型转成 FileChange::Add、FileChange::Delete 或 FileChange::Update;更新文件时还会生成简化的 unified diff(统一差异格式,就是常见的带 + 和 - 的改动文本)→ 出来是一个从路径到文件变化的表。

调用关系:它被 push_delta 用在流式进度事件里;更新文件的差异文本由 format_update_chunks_for_progress 生成,路径来自每个 hunk 的源路径。

调用图:被 1 处调用(push_delta);外部调用 1 个(iter)。

hunk_source_path162–168 ↗
fn hunk_source_path(hunk: &Hunk) -> &Path

作用:从一个补丁改动块里取出它对应的原始文件路径。不管这个改动是新增、删除还是更新,都能统一拿到路径。

数据流:进去一个 Hunk → 它根据 Hunk 的具体种类取出 path 字段 → 出来是这个改动块的文件路径引用,不改动任何状态。

调用关系:它是补丁转换时的小帮手,用来让其他代码不用分别关心新增、删除、更新三种结构里的路径字段在哪里。

format_update_chunks_for_progress170–200 ↗
fn format_update_chunks_for_progress(chunks: &[codex_apply_patch::UpdateFileChunk]) -> String

作用:把“更新文件”的多个小改动块整理成给进度事件看的差异文本。读者会看到类似 git diff 里带 @@、-、+ 的内容。

数据流:进去一组 UpdateFileChunk,每个块里有上下文、旧行、新行和是否文件结尾的信息 → 它拼出一段字符串:上下文行前放 @@,删除的旧行前放 -,新增的新行前放 +,必要时标记文件结束 → 出来是一段 unified diff 风格文本。

调用关系:它服务于 convert_apply_patch_hunks_to_protocol,在流式展示补丁进度时让更新内容更直观。

调用图:外部调用 1 个(new)。

file_paths_for_action202–220 ↗
fn file_paths_for_action(action: &ApplyPatchAction) -> Vec<AbsolutePathBuf>

作用:找出这次补丁会碰到的所有文件路径,包括被修改的文件和移动文件时的新位置。后面权限检查要靠这份清单。

数据流:进去一个已经解析好的 ApplyPatchAction,里面有当前工作目录和所有文件变化 → 它遍历每个变化,把相对路径按当前目录转成绝对路径;如果是移动文件,还把目标路径也加进去 → 出来是一组绝对路径。

调用关系:它被 effective_patch_permissions 调用,是计算写权限之前的第一步;路径转换交给 to_abs_path。

调用图:调用 2 个内部函数(changes, to_abs_path);被 1 处调用(effective_patch_permissions);外部调用 1 个(new)。

to_abs_path222–224 ↗
fn to_abs_path(cwd: &AbsolutePathBuf, path: &Path) -> Option<AbsolutePathBuf>

作用:把补丁里的路径转换成绝对路径,避免权限判断时因为相对路径而看错地方。

数据流:进去当前工作目录 cwd 和一个可能是相对的 path → 它调用 resolve_path_against_base,把 path 放到 cwd 下面解析 → 出来是 AbsolutePathBuf,也就是明确的绝对路径。

调用关系:它被 file_paths_for_action 用来统一路径格式;这是后续沙箱权限判断能准确工作的基础。

调用图:调用 1 个内部函数(resolve_path_against_base);被 1 处调用(file_paths_for_action)。

write_permissions_for_paths226–256 ↗
fn write_permissions_for_paths(
    file_paths: &[AbsolutePathBuf],
    file_system_sandbox_policy: &codex_protocol::permissions::FileSystemSandboxPolicy,
    cwd: &AbsolutePathBuf,
) -> Option<Additi

作用:算出为了写这些文件,还需要临时申请哪些目录的写权限。它只申请当前沙箱还不允许写的地方。

数据流:进去目标文件路径列表、当前文件系统沙箱规则、当前工作目录 → 它取每个文件的父目录,过滤掉已经允许写的目录,去重后做成 AdditionalPermissionProfile(额外权限申请单);如果没有缺的权限就返回空;最后还会规范化权限格式 → 出来是可选的额外权限配置。

调用关系:它被 effective_patch_permissions 调用;它和 effective_file_system_sandbox_policy、apply_granted_turn_permissions 配合,决定这次补丁能不能在安全围栏内写文件,或者是否需要用户/系统批准。

调用图:调用 2 个内部函数(from_read_write_roots, normalize_additional_permissions);被 1 处调用(effective_patch_permissions);外部调用 3 个(default, iter, vec!)。

apply_patch_payload_command259–264 ↗
fn apply_patch_payload_command(payload: &ToolPayload) -> Option<String>

作用:从工具输入里取出原始补丁文本,用来传给 hook。hook 可以理解成工具执行前后插入的一段可定制检查或改写流程。

数据流:进去一个 ToolPayload → 如果它是 Custom 类型,就把里面的 input 克隆出来;如果不是 apply_patch 这种自由文本输入,就返回空 → 出来是补丁命令字符串或空。

调用关系:它被 pre_tool_use_payload 调用,也被 post_tool_use_payload 用来组装 hook 需要看的输入。

调用图:被 1 处调用(pre_tool_use_payload)。

effective_patch_permissions266–307 ↗
async fn effective_patch_permissions(
    session: &Session,
    turn: &TurnContext,
    environment_id: &str,
    action: &ApplyPatchAction,
    cwd: &AbsolutePathBuf,
) -> (
    Vec<AbsolutePathBuf>

作用:把“补丁要改哪些文件”和“当前已经有什么权限”合在一起,算出这次真正生效的权限结果。

数据流:进去会话、当前回合、环境 ID、补丁动作和当前目录 → 它先用 file_paths_for_action 找出会写的文件;再合并会话级和本回合已授予的权限;用这些权限生成实际沙箱规则;然后用 write_permissions_for_paths 算出还缺哪些写权限,并通过 apply_granted_turn_permissions 尝试套用本回合已批准的权限 → 出来是目标文件路径、额外权限结果、以及最终文件系统沙箱规则。

调用关系:它被 ApplyPatchHandler::handle_call 和 intercept_apply_patch 共用,保证无论 apply_patch 是作为工具直接调用,还是从普通命令里拦截出来,都走同一套权限计算。

调用图:调用 7 个内部函数(file_system_sandbox_policy, apply_granted_turn_permissions, file_paths_for_action, write_permissions_for_paths, effective_file_system_sandbox_policy, merge_permission_profiles, as_path);被 2 处调用(handle_call, intercept_apply_patch);外部调用 2 个(granted_session_permissions, granted_turn_permissions)。

ApplyPatchHandler::tool_name310–312 ↗
fn tool_name(&self) -> ToolName

作用:告诉工具系统,这个处理器对应的工具名字叫 apply_patch。

数据流:进去处理器自身 → 它构造一个普通工具名 ToolName::plain("apply_patch") → 出来是工具名对象。

调用关系:它是 ToolExecutor 接口的一部分,工具注册和调度时会用这个名字把调用路由到 ApplyPatchHandler。

调用图:调用 1 个内部函数(plain)。

ApplyPatchHandler::spec314–316 ↗
fn spec(&self) -> ToolSpec

作用:生成 apply_patch 工具对外展示的说明书,也就是模型应该如何调用它。

数据流:进去处理器自身,里面带着是否支持多环境的开关 → 它调用 create_apply_patch_freeform_tool 生成工具规格 → 出来是 ToolSpec。

调用关系:它是 ToolExecutor 接口的一部分;工具系统会用它告诉模型 apply_patch 的输入形式,以及是否能指定环境。

调用图:调用 1 个内部函数(create_apply_patch_freeform_tool)。

ApplyPatchHandler::handle318–320 ↗
fn handle(&self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'_>

作用:接收一次 apply_patch 工具调用,并把异步执行流程包装成工具系统需要的 Future。Future 可以理解成“还没完成、之后会给结果的任务”。

数据流:进去一个 ToolInvocation,包含会话、回合、调用 ID、输入等 → 它把 handle_call 放进 Box::pin,交给异步运行时执行 → 出来是一个异步任务句柄,最终会产出工具输出或错误。

调用关系:它是 ToolExecutor 接口的执行入口;真正的大流程在 ApplyPatchHandler::handle_call 里完成。

调用图:调用 1 个内部函数(handle_call);外部调用 1 个(pin)。

ApplyPatchHandler::handle_call324–472 ↗
async fn handle_call(
        &self,
        invocation: ToolInvocation,
    ) -> Result<Box<dyn crate::tools::context::ToolOutput>, FunctionCallError>

作用:这是 apply_patch 作为工具被直接调用时的主流程:检查输入、验证补丁、算权限、发事件、执行修改、返回结果。

数据流:进去一次工具调用,里面有补丁文本、会话、当前回合、环境信息和追踪器 → 它先确认输入是自由文本;解析补丁;检查是否允许选择环境;找到目标环境和当前目录;用目标环境的文件系统验证补丁是否真的能应用;再调用 effective_patch_permissions 算权限。验证通过后,如果 apply_patch 模块能直接给结果,就包装成工具输出;如果需要运行时执行,就创建事件发送器、ApplyPatchRequest、ToolOrchestrator 和 ApplyPatchRuntime,执行后收集输出和文件差异,最后发完成事件并返回文本结果 → 出来是 ApplyPatchToolOutput,或者给模型看的错误信息。

调用关系:它被 ApplyPatchHandler::handle 调用,是直接工具调用的核心。它会把权限计算交给 effective_patch_permissions,把实际执行交给 apply_patch 模块或 ApplyPatchRuntime,并用 ToolEmitter 通知外部“开始改”和“改完了”。

调用图:调用 11 个内部函数(apply_patch, convert_apply_patch_to_protocol, from_text, boxed_tool_output, apply_patch_for_environment, new, effective_patch_permissions, require_environment_id, resolve_tool_environment, new (+1 more));被 1 处调用(handle);外部调用 5 个(parse_patch, verify_apply_patch_args, format!, RespondToModel, trace!)。

ApplyPatchHandler::matches_kind476–478 ↗
fn matches_kind(&self, payload: &ToolPayload) -> bool

作用:判断某个工具输入是不是这个处理器能接的类型。apply_patch 只接自由文本补丁。

数据流:进去一个 ToolPayload → 它检查这个 payload 是否是 Custom 类型 → 出来 true 或 false,不修改任何东西。

调用关系:它是 CoreToolRuntime 接口的一部分,工具运行时用它来判断当前调用是否应该交给 ApplyPatchHandler。

调用图:外部调用 1 个(matches!)。

ApplyPatchHandler::create_diff_consumer480–482 ↗
fn create_diff_consumer(&self) -> Option<Box<dyn ToolArgumentDiffConsumer>>

作用:为一次 apply_patch 调用创建一个“边收参数边解析”的消费者,用来支持补丁进度流式展示。

数据流:进去处理器自身 → 它创建默认的 ApplyPatchArgumentDiffConsumer,里面带一个新的流式补丁解析器和空缓存 → 出来是装箱后的 ToolArgumentDiffConsumer。

调用关系:它是 CoreToolRuntime 接口的一部分;工具框架在收到流式参数时会用这个消费者,并进一步调用 consume_diff 和 finish。

调用图:外部调用 1 个(default)。

ApplyPatchHandler::pre_tool_use_payload484–489 ↗
fn pre_tool_use_payload(&self, invocation: &ToolInvocation) -> Option<PreToolUsePayload>

作用:在 apply_patch 真正执行前,准备给 hook 看的输入。这样外部规则可以提前检查或改写补丁。

数据流:进去一次工具调用 → 它用 apply_patch_payload_command 取出补丁文本;如果取到了,就包装成 JSON,字段名是 command,并标明 hook 工具名是 apply_patch → 出来是 PreToolUsePayload 或空。

调用关系:它是 CoreToolRuntime 的执行前 hook 支持点;原始补丁文本的提取交给 apply_patch_payload_command。

调用图:调用 1 个内部函数(apply_patch_payload_command)。

ApplyPatchHandler::with_updated_hook_input491–504 ↗
fn with_updated_hook_input(
        &self,
        mut invocation: ToolInvocation,
        updated_input: serde_json::Value,
    ) -> Result<ToolInvocation, FunctionCallError>

作用:如果执行前 hook 改写了补丁输入,这个函数把改写后的内容放回工具调用里。

数据流:进去原来的 ToolInvocation 和 hook 返回的新 JSON → 它调用 updated_hook_command 从 JSON 里取出新的 command;如果原 payload 是 Custom,就把 input 替换成新的补丁文本;其他 payload 保持不变 → 出来是更新后的 ToolInvocation,或者格式错误。

调用关系:它是 CoreToolRuntime 的 hook 改写入口;它让 pre-tool hook 不只是看补丁,还能实际调整即将执行的补丁。

调用图:调用 1 个内部函数(updated_hook_command)。

ApplyPatchHandler::post_tool_use_payload506–521 ↗
fn post_tool_use_payload(
        &self,
        invocation: &ToolInvocation,
        result: &dyn crate::tools::context::ToolOutput,
    ) -> Option<PostToolUsePayload>

作用:在 apply_patch 执行后,准备给 hook 的结果报告。外部规则可以据此记录、审计或做后续动作。

数据流:进去工具调用和工具输出 → 它先从输出里生成适合 hook 的响应内容;再取出原始补丁命令;最后把工具名、调用 ID、输入 command 和响应打包 → 出来是 PostToolUsePayload 或空。

调用关系:它是 CoreToolRuntime 的执行后 hook 支持点;它依赖工具输出的 post_tool_use_response,也复用 apply_patch_payload_command 提供原始命令。

调用图:调用 2 个内部函数(apply_patch, post_tool_use_response);外部调用 1 个(json!)。

intercept_apply_patch525–630 ↗
async fn intercept_apply_patch(
    command: &[String],
    cwd: &AbsolutePathBuf,
    fs: &dyn ExecutorFileSystem,
    turn_environment: TurnEnvironment,
    session: Arc<Session>,
    turn: Arc<Turn

作用:拦截普通执行命令里的 apply_patch。如果用户或模型把 apply_patch 当 shell 命令写出来,它会尽量识别出来,并改走同一套安全补丁流程。

数据流:进去命令参数、当前目录、文件系统、运行环境、会话、回合、差异追踪器、调用 ID 和工具名 → 它先建立沙箱上下文,然后尝试把命令解析并验证成 apply_patch;如果不是 apply_patch 或只是 shell 解析失败,就返回 None,让普通命令继续走原路;如果是合法补丁,就调用 effective_patch_permissions 算权限,再调用 apply_patch 模块决定直接返回还是交给 ApplyPatchRuntime;运行时执行时会发开始/结束事件,收集输出和差异 → 出来是 Some(FunctionToolOutput) 表示已拦截并执行,或 None 表示不拦截,或者错误。

调用关系:它被 run_exec_like 和 handle_call 所在的执行链路使用,用来把命令式 apply_patch 并入工具式 apply_patch 的安全体系。它复用 effective_patch_permissions、ToolOrchestrator、ApplyPatchRuntime 和 ToolEmitter,避免两条路径行为不一致。

调用图:调用 10 个内部函数(apply_patch, convert_apply_patch_to_protocol, from_text, apply_patch_for_environment, new, effective_patch_permissions, new, new, plain, from_abs_path);被 2 处调用(run_exec_like, handle_call);外部调用 4 个(maybe_parse_apply_patch_verified, format!, RespondToModel, trace!)。

require_environment_id632–643 ↗
fn require_environment_id(
    parsed_environment_id: Option<&str>,
    allow_environment_id: bool,
) -> Result<Option<String>, FunctionCallError>

作用:检查补丁里写的环境 ID 是否被允许使用。多环境没开启时,不能偷偷指定目标环境。

数据流:进去解析出来的环境 ID 和 allow_environment_id 开关 → 如果补丁指定了环境但当前不允许,就返回给模型看的错误;如果允许,就把环境 ID 转成 String;如果没指定,就返回 None → 出来是可选环境 ID 或错误。

调用关系:它被 ApplyPatchHandler::handle_call 在解析补丁后立刻调用,挡住不被允许的环境选择,防止补丁发到不该去的地方。

调用图:被 1 处调用(handle_call);外部调用 1 个(RespondToModel)。

core/src/apply_patch.rs源码 ↗
orchestrationrequest handling

当模型想用 apply_patch 改代码时,系统不能马上动手,因为这可能会改坏文件、越权写入,或者绕过用户设置的沙盒。这个文件就像门卫:先看这次补丁要改哪些文件、用户的审批规则是什么、当前沙盒允许什么。安全就放行给运行环境去真正改文件;需要人工确认就标记为“要审批”;明显不安全就把拒绝原因返回给模型。它自己不负责把补丁写进磁盘,而是做判断和转交。另外,它还把 apply_patch 库里的“新增、删除、更新文件”表示,翻译成协议层的 FileChange 格式,方便后面的调用链用同一种语言描述文件变化。

函数细节2
apply_patch33–74 ↗
async fn apply_patch(
    turn_context: &TurnContext,
    file_system_sandbox_policy: &FileSystemSandboxPolicy,
    action: ApplyPatchAction,
) -> InternalApplyPatchInvocation

作用:这个函数是补丁请求的安全分流口。它根据当前会话、用户审批策略、沙盒限制和补丁内容,决定这次改文件能不能继续。

数据流:进去的是当前对话环境、文件系统沙盒规则,以及一份要执行的补丁动作。它调用 assess_patch_safety 做安全评估,再根据结果分三种情况:安全则包装成 DelegateToRuntime 交给运行环境执行;需要确认则也交给运行环境,但带上“需要用户批准”的标记;被拒绝则生成一条错误信息返回给模型。它不直接改文件,只输出下一步该怎么处理。

调用关系:handle_call 和 intercept_apply_patch 在遇到 apply_patch 请求时会调用它。它把真正的安全判断交给 assess_patch_safety,并读取 turn_context 里的 permission_profile 等信息;最后把结果交回上层,让上层决定是继续走运行时执行流程,还是把拒绝信息返回。

调用图:调用 2 个内部函数(assess_patch_safety, permission_profile);被 2 处调用(handle_call, intercept_apply_patch);外部调用 4 个(DelegateToRuntime, Output, format!, RespondToModel)。

convert_apply_patch_to_protocol76–100 ↗
fn convert_apply_patch_to_protocol(
    action: &ApplyPatchAction,
) -> HashMap<PathBuf, FileChange>

作用:这个函数把补丁里的文件变化翻译成协议层使用的格式。这样系统其他部分不用懂 apply_patch 库自己的内部表示,也能知道哪些文件要新增、删除或更新。

数据流:进去的是一份 ApplyPatchAction,也就是补丁动作。它读取 action.changes() 里的每个文件变化:新增就复制新增内容,删除就复制原内容,更新就复制差异文本和可能的移动路径。最后出来的是一个以文件路径为键、FileChange 为值的表,表示每个文件将发生什么变化。

调用关系:handle_call 和 intercept_apply_patch 会在需要把补丁变化告诉协议层或调用方时使用它。它只做格式转换,不判断安全,也不执行写文件;前面的 apply_patch 负责审批分流,后续流程再使用这里生成的 FileChange 数据。

调用图:调用 1 个内部函数(changes);被 2 处调用(handle_call, intercept_apply_patch);外部调用 1 个(with_capacity)。

core/src/tools/runtimes/apply_patch.rs源码 ↗
orchestrationrequest handling

这个文件可以理解成 apply_patch 工具的“现场施工负责人”。上游已经检查过补丁内容是否安全,但真正动手改文件时,还要确认权限、选择沙箱(一种受限制的运行环境,像给施工队划定施工区域)、记录改了哪些东西,并把输出结果交回给调度器。它把一次补丁请求包装成 ApplyPatchRequest,里面有工作目录、补丁文本、会改到的文件、审批要求等信息。运行时 ApplyPatchRuntime 会先配合审批系统:能复用之前批准的就复用,需要用户或 Guardian 审核的就发请求。真正执行时,它从当前回合的环境里拿到文件系统,把沙箱限制转成底层能懂的格式,然后调用 codex_apply_patch 去改文件。无论成功还是失败,它都会把已经提交的改动累积到 committed_delta 里;如果失败看起来是沙箱拦住了,还会把错误标成“沙箱拒绝”,方便外层决定是否申请更高权限再试。

函数细节13
ApplyPatchRuntime::new69–71 ↗
fn new() -> Self

作用:创建一个新的补丁运行器。外层在准备处理一次 apply_patch 调用时会用它来拿到一个干净的运行对象。

数据流:进去没有额外参数 → 它调用默认构造方式,建立一个 committed_delta 为空的 ApplyPatchRuntime → 出来一个可用于执行补丁的新运行器。

调用关系:它是入口式的小工厂函数。调用图里显示 handle_call、intercept_apply_patch 和多个测试会用它先造出运行器,之后再走审批、沙箱和执行流程。

调用图:被 6 处调用(handle_call, intercept_apply_patch, approval_keys_include_environment_id, permission_request_payload_uses_apply_patch_hook_name_and_aliases, sandbox_cwd_uses_patch_action_cwd, wants_no_sandbox_approval_granular_respects_sandbox_flag);外部调用 1 个(default)。

ApplyPatchRuntime::committed_delta73–75 ↗
fn committed_delta(&self) -> &AppliedPatchDelta

作用:查看这个运行器到目前为止已经实际提交过的补丁改动。有人想知道“到底已经改了哪些文件和内容”时会用它。

数据流:进去是当前运行器本身 → 它不改任何东西,只拿出内部保存的 committed_delta 引用 → 出来的是一份只读视角的累计改动记录。

调用关系:它是执行后的查询口。ApplyPatchRuntime::run 会不断把新改动追加到 committed_delta,这个函数则让外层或测试能读到这份累计账本。

ApplyPatchRuntime::build_guardian_review_request77–87 ↗
fn build_guardian_review_request(
        req: &ApplyPatchRequest,
        call_id: &str,
    ) -> GuardianApprovalRequest

作用:把一次补丁申请整理成 Guardian 审核系统能看懂的格式。Guardian 可以理解成一个额外的安全审查员。

数据流:进去是一份 ApplyPatchRequest 和本次调用的 call_id → 它取出工作目录、涉及文件和补丁文本,连同 id 一起包装 → 出来一个 GuardianApprovalRequest::ApplyPatch 审核请求。

调用关系:它服务于审批流程。ApplyPatchRuntime::start_approval_async 在发现已有 Guardian review id 时会调用它,然后把整理好的请求交给 review_approval_request 去做真正的审核。测试 guardian_review_request_includes_patch_context 也会检查它是否把上下文带全。

调用图:被 2 处调用(start_approval_async, guardian_review_request_includes_patch_context)。

ApplyPatchRuntime::file_system_sandbox_context_for_attempt89–106 ↗
fn file_system_sandbox_context_for_attempt(
        req: &ApplyPatchRequest,
        attempt: &SandboxAttempt<'_>,
    ) -> Option<FileSystemSandboxContext>

作用:为某一次执行尝试准备文件系统沙箱设置。简单说,就是告诉底层“这次施工能在哪个目录动手、有哪些权限限制”。

数据流:进去是一份补丁请求和一次沙箱尝试的信息 → 如果这次不使用沙箱,就直接返回空;否则它合并基础权限和额外权限,填入工作目录、Windows 沙箱级别、Landlock 等限制 → 出来一个 FileSystemSandboxContext,供真正执行补丁时使用。

调用关系:它位于执行前的准备阶段。ApplyPatchRuntime::run 会依赖它生成沙箱上下文,再传给底层 apply_patch;测试 file_system_sandbox_context_uses_active_attempt 会验证它使用的是当前这次尝试的沙箱信息。

调用图:调用 1 个内部函数(effective_permission_profile);被 1 处调用(file_system_sandbox_context_uses_active_attempt)。

ApplyPatchRuntime::sandbox_preference110–112 ↗
fn sandbox_preference(&self) -> SandboxablePreference

作用:告诉调度器这个工具倾向于自动选择沙箱方式。也就是别固定写死,而是按平台和策略来决定。

数据流:进去是当前运行器 → 它不读取请求内容,只返回 SandboxablePreference::Auto → 出来一个“自动选择沙箱”的偏好值。

调用关系:这是 Sandboxable 接口的一部分。调度器在安排工具运行环境时会询问它,从而决定是否以及如何套上沙箱。

ApplyPatchRuntime::escalate_on_failure113–115 ↗
fn escalate_on_failure(&self) -> bool

作用:告诉调度器:如果在沙箱里失败了,可以考虑提高权限后再试。这里的“提高权限”不是随便放开,而是走审批流程。

数据流:进去是当前运行器 → 它直接返回 true → 出来一个信号,表示失败后允许进入更高权限的重试路径。

调用关系:这是 Sandboxable 接口的一部分。它和 ApplyPatchRuntime::run 里识别“疑似沙箱拒绝”的逻辑配合,让外层知道哪些失败可能值得申请权限后重跑。

ApplyPatchRuntime::approval_keys121–130 ↗
fn approval_keys(&self, req: &ApplyPatchRequest) -> Vec<Self::ApprovalKey>

作用:为这次补丁生成一组“审批缓存钥匙”。同一个环境里的同一个文件如果已经批准过,就可以避免重复打扰用户。

数据流:进去是一份 ApplyPatchRequest → 它遍历这次会碰到的每个文件路径,并把路径和当前环境 id 绑在一起 → 出来一组 ApplyPatchApprovalKey。

调用关系:它服务于 ApplyPatchRuntime::start_approval_async。审批流程会用这些 key 调用 with_cached_approval,判断这次是否能沿用之前的批准结果。

调用图:被 1 处调用(start_approval_async)。

ApplyPatchRuntime::start_approval_async132–181 ↗
fn start_approval_async(
        &'a mut self,
        req: &'a ApplyPatchRequest,
        ctx: ApprovalCtx<'a>,
    ) -> BoxFuture<'a, ReviewDecision>

作用:启动补丁执行前的审批流程。它决定是找 Guardian 审核、直接通过、让用户重新批准,还是用缓存审批结果。

数据流:进去是补丁请求和审批上下文,里面有会话、回合、调用 id、重试原因等 → 它先看是否走 Guardian;再看权限是否已预先批准;如果是失败重试,就带着原因重新向用户请求;普通情况则用 approval_keys 查缓存,缓存没有才发起用户审批 → 出来一个 ReviewDecision,表示批准、拒绝或默认结果。

调用关系:它是审批这条支线的核心。它会调用 ApplyPatchRuntime::approval_keys 生成缓存依据,必要时调用 ApplyPatchRuntime::build_guardian_review_request,再把请求交给 review_approval_request、session.request_patch_approval 或 with_cached_approval。

调用图:调用 3 个内部函数(approval_keys, build_guardian_review_request, with_cached_approval);外部调用 2 个(pin, review_approval_request)。

ApplyPatchRuntime::wants_no_sandbox_approval183–191 ↗
fn wants_no_sandbox_approval(&self, policy: AskForApproval) -> bool

作用:判断在“无沙箱”运行前,是否应该专门要一次批准。无沙箱就是不再用受限环境,风险更高,所以要看全局策略怎么说。

数据流:进去是 AskForApproval 策略 → 它按不同策略判断:Never 不要,Granular 看细粒度配置是否允许沙箱审批,其它常见需要人工把关的策略返回要 → 出来一个布尔值,表示是否想要无沙箱审批。

调用关系:它是 Approvable 接口的一部分。调度器在考虑从沙箱升级到无沙箱时会询问它,测试 wants_no_sandbox_approval_granular_respects_sandbox_flag 会检查细粒度策略分支。

ApplyPatchRuntime::exec_approval_requirement197–202 ↗
fn exec_approval_requirement(
        &self,
        req: &ApplyPatchRequest,
    ) -> Option<ExecApprovalRequirement>

作用:把这次 apply_patch 自己算好的执行审批要求交给调度器。这样调度器不会错误地套用普通命令执行的审批规则。

数据流:进去是一份 ApplyPatchRequest → 它取出 req.exec_approval_requirement 并克隆一份 → 出来一个明确的 ExecApprovalRequirement。

调用关系:这是 Approvable 接口的覆盖实现。注释说明 apply_patch 的安全判断已经在上游 assess_patch_safety 做过,这里只是确保外层按补丁专属规则启动审批流程。

ApplyPatchRuntime::permission_request_payload204–212 ↗
fn permission_request_payload(
        &self,
        req: &ApplyPatchRequest,
    ) -> Option<PermissionRequestPayload>

作用:生成请求额外权限时展示给用户或钩子系统看的内容。它说明这次工具名是 apply_patch,输入是补丁文本。

数据流:进去是一份 ApplyPatchRequest → 它把工具名设为 apply_patch,并把补丁文本放进 JSON(一种常见的键值格式)里的 command 字段 → 出来一个 PermissionRequestPayload。

调用关系:它服务于权限申请流程。调度器需要展示或记录“哪个工具因为什么输入要权限”时会用它;测试 permission_request_payload_uses_apply_patch_hook_name_and_aliases 会检查工具名和别名是否正确。

调用图:调用 1 个内部函数(apply_patch);外部调用 1 个(json!)。

ApplyPatchRuntime::sandbox_cwd216–218 ↗
fn sandbox_cwd(&self, req: &'a ApplyPatchRequest) -> Option<&'a AbsolutePathBuf>

作用:告诉沙箱系统这次补丁应该以哪个目录作为工作目录。工作目录就像施工队进场时站在哪个房间开始干活。

数据流:进去是一份 ApplyPatchRequest → 它读取 req.action.cwd → 出来这个 cwd 的引用,作为沙箱和执行时的起点目录。

调用关系:这是 ToolRuntime 接口的一部分。调度器准备沙箱时会问它要工作目录,测试 sandbox_cwd_uses_patch_action_cwd 会确认它确实使用补丁动作里的 cwd。

ApplyPatchRuntime::run220–267 ↗
async fn run(
        &mut self,
        req: &ApplyPatchRequest,
        attempt: &SandboxAttempt<'_>,
        _ctx: &ToolCtx,
    ) -> Result<ApplyPatchRuntimeOutput, ToolError>

作用:真正执行补丁,把文件改掉,并把结果包装成工具输出。它是这个文件里“动手施工”的那一步。

数据流:进去是补丁请求、当前沙箱尝试和工具上下文 → 它记录开始时间,从回合环境拿文件系统,生成沙箱上下文,调用底层 codex_apply_patch::apply_patch 写文件,同时收集标准输出和错误输出;然后把成功或失败对应的改动追加到 committed_delta,组装退出码、输出文本、耗时等信息;如果失败看起来是沙箱拒绝,就返回 SandboxErr::Denied → 出来要么是 ApplyPatchRuntimeOutput,要么是 ToolError,同时运行器内部的累计改动已更新。

调用关系:它是 ToolRuntime 的主执行函数。审批和沙箱决策完成后,调度器会调用它;它会用 ApplyPatchRuntime::file_system_sandbox_context_for_attempt 准备限制条件,调用底层 apply_patch 完成文件修改,再用 is_likely_sandbox_denied 帮外层判断是否需要走权限升级重试。

调用图:调用 3 个内部函数(append, is_likely_sandbox_denied, new);外部调用 10 个(new, now, file_system_sandbox_context_for_attempt, from_utf8_lossy, new, clone, apply_patch, Codex, format!, Sandbox)。

补丁解析流程

这些文件将原始补丁文本或调用语法转换为经过验证的结构化补丁参数,并支持增量解析。

apply-patch/src/streaming_parser.rs源码 ↗
domain_logicrequest handling / streaming patch parsing

这个文件实现了一个流式补丁解析器。所谓“流式”,就是文本像水龙头滴水一样一小段一小段进来,解析器要记住自己读到哪里了,等凑够一整行再判断。它用一个简单的状态机(按“还没开始、已经开始、正在新增文件、正在删除文件、正在修改文件、已经结束”等状态来做决定)防止把补丁看错。它会识别固定标记,比如“开始补丁”“结束补丁”“新增文件”“修改文件”,并把结果存成 Hunk(一个补丁里的单个改动块)。修改文件时,它还会检查每个小段不能是空的,行必须用空格、加号或减号表示“原样保留、添加、删除”。这样上层程序既能在补丁还在传输时展示进度,也能在最后确认格式正确,避免把半截或坏格式补丁拿去改文件。

函数细节17
StreamingPatchParser::environment_id49–51 ↗
fn environment_id(&self) -> Option<&str>

作用:返回补丁里声明的环境 ID,也就是这段补丁希望应用到哪个环境的名字。没有声明时就返回空。

数据流:进去的是解析器当前保存的状态 → 它查看状态里有没有 environment_id → 出来的是一个只读的字符串引用,或者没有值;它不改动任何内容。

调用关系:这是给外部读取结果的小窗口。补丁文本由 push_delta 和 process_line 解析,handle_hunk_headers_and_end_patch 在遇到环境标记时把值存进去,之后调用者可以用它取出来。

StreamingPatchParser::ensure_update_hunk_is_not_empty53–82 ↗
fn ensure_update_hunk_is_not_empty(&self, line: &str) -> Result<(), ParseError>

作用:检查正在解析的“修改文件”改动块不是空壳。没有这一步,像“说要修改某文件但没写任何改动”的补丁可能会被误认为合法。

数据流:进去的是当前行和解析器里已经收集到的最后一个改动块 → 它看这个改动块是不是 UpdateFile,以及里面是否已经有实际增删内容 → 如果正常就什么也不返回;如果发现空修改块,就返回带行号的解析错误。

调用关系:它是几个关键转场前的安全检查。finish 在收尾时会用它,handle_hunk_headers_and_end_patch 在遇到下一个文件头或结束标记前也会用它,确保上一个修改块先验收合格。

调用图:被 2 处调用(finish, handle_hunk_headers_and_end_patch);外部调用 1 个(format!)。

StreamingPatchParser::handle_hunk_headers_and_end_patch84–137 ↗
fn handle_hunk_headers_and_end_patch(&mut self, trimmed: &str) -> Result<bool, ParseError>

作用:识别一行是不是补丁里的“大标题”或结束标记,比如新增文件、删除文件、修改文件、结束补丁、环境 ID。识别到了就更新解析器状态。

数据流:进去的是去掉首尾空白后的当前行 → 它按固定前缀判断这行代表什么,并在需要时先检查前一个修改块不为空 → 出来的是 true 或 false:true 表示这行已经被当作标题处理;同时可能新增一个 Hunk、保存 environment_id,或把状态改成已结束。

调用关系:process_line 在不同解析状态下都会先请它看看当前行是不是结构性标记。它自己会调用 ensure_update_hunk_is_not_empty,防止从一个空的修改块直接跳到下一个块或结束。

调用图:调用 1 个内部函数(ensure_update_hunk_is_not_empty);被 1 处调用(process_line);外部调用 4 个(from, new, new, matches!)。

StreamingPatchParser::push_delta139–152 ↗
fn push_delta(&mut self, delta: &str) -> Result<Vec<Hunk>, ParseError>

作用:接收一小段新到达的补丁文本,并尽量解析其中已经完整的行。它让补丁可以边传边被理解,而不是必须等全部传完。

数据流:进去的是一段字符串 delta,可能只有几个字符,也可能有很多行 → 它逐字符扫描,遇到换行才把缓存的一整行交给 process_line;没遇到换行的半截行先存在 line_buffer 里 → 出来的是目前已经解析出的改动块列表副本;解析器内部的缓存、行号和状态会被更新。

调用关系:这是流式解析的主要入口。调用图显示它在持续推送片段的过程中会反复被使用;每凑齐一行,它就把具体判断交给 process_line。

调用图:调用 1 个内部函数(process_line);被 1 处调用(push_delta);外部调用 1 个(take)。

StreamingPatchParser::finish154–173 ↗
fn finish(&mut self) -> Result<Vec<Hunk>, ParseError>

作用:告诉解析器“输入结束了”,让它处理最后一行,并确认补丁真的以结束标记收尾。没有它,最后一行没有换行时可能漏解析,半截补丁也可能被误用。

数据流:进去的是解析器当前缓存和状态 → 如果还有没带换行的最后一行,它会处理这行;如果最后没有进入“已结束补丁”状态,就返回错误 → 出来的是最终改动块列表副本,或者一个说明补丁没正确结束的错误。

调用关系:它是流式输入完成后的收口步骤。它可能调用 process_line 处理最后一行,也会调用 ensure_update_hunk_is_not_empty 检查最后的修改块;调用图里 finish_update_on_complete 会在完整工具调用结束时使用它。

调用图:调用 2 个内部函数(ensure_update_hunk_is_not_empty, process_line);被 1 处调用(finish_update_on_complete);外部调用 2 个(matches!, take)。

StreamingPatchParser::process_line175–408 ↗
fn process_line(&mut self, line: &str) -> Result<(), ParseError>

作用:解析单独一行补丁文本,这是整个解析器的核心判断器。它根据当前状态决定这一行是标题、文件内容、修改内容,还是格式错误。

数据流:进去的是一整行文本 → 它先看解析器现在处于哪个阶段,再按规则处理:开始标记会开启补丁,新增文件里的加号行会追加到文件内容,修改文件里的空格、加号、减号行会分别变成上下文、增加、删除内容 → 出来通常是成功并更新内部 Hunk;如果行不符合当前位置的规则,就返回带说明和行号的错误。

调用关系:push_delta 每读到完整一行就调用它,finish 也可能在最后一行没有换行时调用它。它会把标题和结束标记的识别交给 handle_hunk_headers_and_end_patch,自己负责各状态下的具体内容解析。

调用图:调用 1 个内部函数(handle_hunk_headers_and_end_patch);被 2 处调用(finish, push_delta);外部调用 4 个(from, new, new, format!)。

tests::test_streaming_patch_parser_streams_complete_lines_before_end_patch419–480 ↗
fn test_streaming_patch_parser_streams_complete_lines_before_end_patch()

作用:测试解析器能在补丁还没结束时,先返回已经完整读到的改动块。这样上层可以提前展示进度。

数据流:进去的是几段被拆开的补丁文本 → 测试把它们分批喂给默认解析器,并比较每一步返回的 Hunk → 出来是断言通过或失败;它不影响生产代码,只验证行为。

调用关系:这个测试直接使用 StreamingPatchParser::default 和断言宏。它覆盖 push_delta 的核心流式能力,特别是半行内容不会过早进入结果。

调用图:外部调用 2 个(default, assert_eq!)。

tests::test_streaming_patch_parser_environment_id_mode483–519 ↗
fn test_streaming_patch_parser_environment_id_mode()

作用:测试环境 ID 的读取规则:可以写一次,不能为空,不能重复。这样补丁不会模糊地指向多个环境。

数据流:进去的是带环境 ID、重复环境 ID、空环境 ID 的补丁文本 → 测试分别推入解析器,并读取 environment_id 或检查错误 → 出来是预期的改动块、环境 ID,或指定错误。

调用关系:它验证 handle_hunk_headers_and_end_patch 保存环境 ID 的逻辑,也验证 StreamingPatchParser::environment_id 这个读取接口是否能拿到正确值。

调用图:外部调用 2 个(default, assert_eq!)。

tests::test_streaming_patch_parser_large_patch_split_by_character522–610 ↗
fn test_streaming_patch_parser_large_patch_split_by_character()

作用:测试一个很大的补丁即使被拆成一个字符一个字符传入,也能稳定解析。它模拟最极端的流式输入情况。

数据流:进去的是包含新增、修改、删除、移动等多种操作的大补丁 → 测试逐字符调用 push_delta,记录已看到的改动块数量是否只增不减 → 出来是最终 7 个改动块,以及它们类型顺序的断言结果。

调用关系:这个测试反复驱动 push_delta,间接覆盖 process_line 和 handle_hunk_headers_and_end_patch 在复杂连续补丁中的配合。

调用图:外部调用 4 个(new, default, assert!, assert_eq!)。

tests::test_streaming_patch_parser_keeps_indented_update_markers_as_context_lines613–649 ↗
fn test_streaming_patch_parser_keeps_indented_update_markers_as_context_lines()

作用:测试带前导空格的“*** Update File”不会被误认成新的文件标题,而是当作普通上下文行。这样文件内容里恰好出现类似标记时不会解析错。

数据流:进去的是一个修改补丁,其中一行以空格开头,内容看起来像更新文件标题 → 解析器把它当作上下文行放进 old_lines 和 new_lines → 出来是只有一个 UpdateFile,里面包含两个修改片段。

调用关系:它重点验证 process_line 在 UpdateFile 状态下对行首空格的处理,确保 handle_hunk_headers_and_end_patch 只处理真正处在结构位置的标题。

调用图:外部调用 2 个(default, assert_eq!)。

tests::test_streaming_patch_parser_preserves_bare_empty_update_lines652–687 ↗
fn test_streaming_patch_parser_preserves_bare_empty_update_lines()

作用:测试修改块里的纯空行会被保留下来,当作一行空的上下文。这样补丁不会无意中丢掉文件里的空白行。

数据流:进去的是一个修改文件补丁,中间有一行完全空白 → 解析器把这行同时加入旧内容和新内容 → 出来是包含空字符串上下文行的 UpdateFileChunk。

调用关系:它验证 process_line 对空行的宽容处理,并确保流式解析器和普通解析器的行为一致。

调用图:外部调用 2 个(default, assert_eq!)。

tests::test_streaming_patch_parser_ignores_empty_lines_after_end_of_file690–707 ↗
fn test_streaming_patch_parser_ignores_empty_lines_after_end_of_file()

作用:测试“文件末尾”标记后面如果只有空行,不会被当成错误。这样补丁文本排版稍有空行也能接受。

数据流:进去的是一个更新补丁,出现 End of File 标记后又有空行,再结束补丁 → 解析器把修改块标记为文件末尾,并忽略那条空行 → 出来是带 is_end_of_file 为 true 的改动块。

调用关系:它覆盖 process_line 在 UpdateFile 状态下看到 EOF_MARKER 后的特殊分支,保证后续空行不会破坏 handle_hunk_headers_and_end_patch 对结束标记的识别。

调用图:外部调用 2 个(default, assert_eq!)。

tests::test_streaming_patch_parser_matches_line_ending_behavior710–740 ↗
fn test_streaming_patch_parser_matches_line_ending_behavior()

作用:测试不同换行格式下解析结果正确,尤其是 Windows 常见的回车换行。这样跨平台生成的补丁不会因为行尾不同而坏掉。

数据流:进去的是带 \r\n 的补丁,以及内容里额外带 \r 的补丁 → push_delta 处理普通行尾时会去掉行尾回车,但保留真正属于内容的回车 → 出来是与预期完全一致的修改块。

调用关系:它验证 push_delta 在遇到换行时整理 line_buffer 的行为,也间接验证 process_line 收到的行内容是否符合预期。

调用图:外部调用 2 个(default, assert_eq!)。

tests::test_streaming_patch_parser_finish_processes_final_line_without_newline743–789 ↗
fn test_streaming_patch_parser_finish_processes_final_line_without_newline()

作用:测试最后一行没有换行符时,finish 仍然会把它处理掉。这样常见的“文件末尾不带换行”不会导致结束标记丢失。

数据流:进去的是结束标记不带最后换行的补丁,以及一个把类似结束标记当作内容的更新补丁 → 先 push_delta,再 finish → 出来是正确的改动块,没有误报缺少结束标记。

调用关系:它直接验证 finish 对 line_buffer 里最后半行的处理,也确认 process_line 在最后收尾时仍按同一套规则工作。

调用图:外部调用 2 个(default, assert_eq!)。

tests::test_streaming_patch_parser_finish_requires_end_patch792–807 ↗
fn test_streaming_patch_parser_finish_requires_end_patch()

作用:测试 finish 必须看到真正的结束标记才算成功。这样半截补丁不会被当成完整补丁去应用。

数据流:进去的是开始了新增文件但没有 End Patch 的文本 → push_delta 能先返回已看到的新增内容,finish 最后检查状态 → 出来是“最后一行必须是结束补丁”的错误。

调用关系:它验证 finish 的最终守门职责:即使 push_delta 已经解析出一些 Hunk,完整性检查仍要在 finish 阶段完成。

调用图:外部调用 2 个(default, assert_eq!)。

tests::test_streaming_patch_parser_rejects_content_after_end_patch810–831 ↗
fn test_streaming_patch_parser_rejects_content_after_end_patch()

作用:测试结束标记后不能再有真正内容,但可以有空白行。这样补丁结束的位置清清楚楚,不会偷偷附带额外文本。

数据流:进去的是 End Patch 后跟 extra 的补丁,以及 End Patch 后只跟空白的补丁 → 解析器分别处理这些后续行 → 出来分别是错误,或正常返回已解析的新增文件。

调用关系:它验证 process_line 在 EndedPatch 状态下的规则:空白可以忽略,非空内容必须报错。

调用图:外部调用 2 个(default, assert_eq!)。

tests::test_streaming_patch_parser_returns_errors834–943 ↗
fn test_streaming_patch_parser_returns_errors()

作用:集中测试各种坏格式补丁都会返回清楚的错误,而不是悄悄解析错。它像一组安全护栏检查。

数据流:进去的是多种错误输入:开头不对、文件标题不合法、新增/删除块里出现乱行、修改块为空、修改块行格式不对等 → 每个输入都喂给解析器 → 出来是精确匹配的错误类型、错误消息和行号。

调用关系:它覆盖 process_line、handle_hunk_headers_and_end_patch 和 ensure_update_hunk_is_not_empty 的错误路径,确保主解析流程在遇到坏补丁时能及时停下并说明原因。

调用图:外部调用 2 个(default, assert_eq!)。

apply-patch/src/parser.rs源码 ↗
domain_logicrequest handling

这个文件像补丁工具的“收件员兼安检员”。它先确认补丁外面有没有正确的开头和结尾,比如必须从“ Begin Patch”开始、到“ End Patch”结束;如果写法不对,就立刻给出清楚的错误。然后它把补丁交给流式解析器,也就是一种可以按文本内容逐步读出结构的解析器,拆成一个个 Hunk。Hunk 可以理解成“一项文件动作”:新增文件、删除文件、更新文件,更新时还可能顺便改名。文件里还保留了宽松模式:有些调用会把 shell 的 heredoc 写法一起传进来,它会尝试把外层的“<<EOF ... EOF”剥掉,只留下真正的补丁。这样能减少因为调用格式小失误导致补丁完全不能用的问题。这里也定义了路径解析方法,保证相对路径能按当前工作目录变成绝对路径,方便后续真正改磁盘文件。

函数细节13
Hunk::resolve_path84–90 ↗
fn resolve_path(&self, cwd: &AbsolutePathBuf) -> AbsolutePathBuf

作用:把一个补丁动作里的文件路径,变成确定的绝对路径。这样后面真正改文件时,不会因为“当前目录是谁”而搞错目标文件。

数据流:进去的是一个 Hunk 和当前工作目录 cwd。它先取出这个 Hunk 影响的路径;如果是更新并改名的动作,会用原文件路径来定位要修改的文件。然后调用路径工具,把相对路径拼到 cwd 下,绝对路径则保持为绝对路径。出来的是一个 AbsolutePathBuf,也就是确定无歧义的文件位置。

调用关系:它会先用 Hunk::path 或直接读取更新动作里的原路径拿到路径,再把活儿交给 resolve_path_against_base 这个路径工具。它通常在后续应用补丁前使用,帮执行阶段找到真正要操作的文件。

调用图:调用 2 个内部函数(path, resolve_path_against_base)。

Hunk::path93–107 ↗
fn path(&self) -> &Path

作用:返回这个补丁动作最终影响的路径。对普通新增、删除来说就是它写的路径;对“更新后移动/改名”来说,返回移动后的目标路径。

数据流:进去的是一个 Hunk。它根据 Hunk 的种类做选择:新增文件返回新增路径,删除文件返回删除路径,更新文件如果有 move_path 就返回新路径,否则返回原路径。出来的是一个 Path 引用,不会复制或改动数据。

调用关系:Hunk::resolve_path 会调用它来拿路径。它像一个小查询接口,让别的代码不用自己判断 Hunk 到底是哪一种。

调用图:被 1 处调用(resolve_path)。

parse_patch129–136 ↗
fn parse_patch(patch: &str) -> Result<ApplyPatchArgs, ParseError>

作用:这是外部最常用的补丁解析入口。别人给它一整段补丁文字,它负责选好解析模式,再返回结构化的 ApplyPatchArgs。

数据流:进去的是原始补丁字符串。它根据当前常量选择严格模式或宽松模式;当前代码默认走宽松模式。然后它把字符串交给 parse_patch_text。出来的是解析好的 ApplyPatchArgs,里面有补丁动作列表、原始补丁文本、工作目录占位和可选的环境 ID;如果格式不对,就出来 ParseError。

调用关系:它被 apply_patch、maybe_parse_apply_patch、maybe_parse_apply_patch_verified 以及多项测试调用,是这套解析功能的门面。真正检查边界和拆内容的工作交给 parse_patch_text。

调用图:调用 1 个内部函数(parse_patch_text);被 10 处调用(apply_patch, maybe_parse_apply_patch, maybe_parse_apply_patch_verified, test_unified_diff_insert_at_eof, test_unified_diff_last_line_replacement, test_unified_diff, test_unified_diff_first_line_replacement, test_unified_diff_insert_at_eof, test_unified_diff_interleaved_changes, test_unified_diff_last_line_replacement)。

parse_patch_text177–195 ↗
fn parse_patch_text(patch: &str, mode: ParseMode) -> Result<ApplyPatchArgs, ParseError>

作用:真正执行补丁解析的核心函数。它先检查补丁外壳,再把正文交给流式解析器变成 Hunk 列表。

数据流:进去的是补丁文字和解析模式。它先 trim 掉首尾空白并按行切开;严格模式下直接检查开头结尾,宽松模式下会先尝试正常检查,失败后再尝试识别 heredoc 外壳。拿到有效行以后,它重新拼回补丁文本,创建 StreamingPatchParser,把文本推给它解析,再 finish 得到 hunks,同时读取 environment_id。出来的是 ApplyPatchArgs;如果边界或内部格式错了,就出来 ParseError。

调用关系:parse_patch 会调用它。它根据模式分别调用 check_patch_boundaries_strict 或 check_patch_boundaries_lenient,之后把细粒度解析工作交给 StreamingPatchParser。

调用图:调用 2 个内部函数(check_patch_boundaries_lenient, check_patch_boundaries_strict);被 1 处调用(parse_patch);外部调用 1 个(default)。

check_patch_boundaries_strict199–207 ↗
fn check_patch_boundaries_strict(lines: &'a [&'a str]) -> Result<&'a [&'a str], ParseError>

作用:用严格规则检查补丁的第一行和最后一行。它确保补丁确实被正确的开始、结束标记包住。

数据流:进去的是按行切好的补丁文本。它取出第一行和最后一行,如果没有行也会按缺失处理,然后交给 check_start_and_end_lines_strict 判断。检查通过就原样返回这些行;检查失败就返回 ParseError。

调用关系:parse_patch_text 在严格模式下直接调用它。check_patch_boundaries_lenient 也会先调用它,因为宽松模式的第一选择仍然是接受标准补丁。

调用图:调用 1 个内部函数(check_start_and_end_lines_strict);被 2 处调用(check_patch_boundaries_lenient, parse_patch_text)。

check_patch_boundaries_lenient216–238 ↗
fn check_patch_boundaries_lenient(
    original_lines: &'a [&'a str],
) -> Result<&'a [&'a str], ParseError>

作用:用更宽容的方式检查补丁外壳,专门兼容把 heredoc 标记也传进来的情况。heredoc 可以理解成 shell 里“下面这一大段文字当作输入”的写法。

数据流:进去的是原始行列表。它先尝试按严格规则检查;如果成功,就直接返回。失败后,它看第一行是不是 <<EOF、<<'EOF' 或 <<"EOF",最后一行是不是以 EOF 结尾,并且总行数足够。如果符合,就去掉第一行和最后一行,再对中间真正的补丁内容做严格检查。出来的是真正补丁内容的行切片;不符合就保留原来的错误。

调用关系:parse_patch_text 在宽松模式下调用它。它内部仍然依赖 check_patch_boundaries_strict,等于先让标准格式过关,再对少数常见误包裹格式做补救。

调用图:调用 1 个内部函数(check_patch_boundaries_strict);被 1 处调用(parse_patch_text)。

check_start_and_end_lines_strict240–258 ↗
fn check_start_and_end_lines_strict(
    first_line: Option<&&str>,
    last_line: Option<&&str>,
) -> Result<(), ParseError>

作用:检查补丁开头和结尾是不是指定的两句标记。它负责给出最直接的错误原因:第一行错了,还是最后一行错了。

数据流:进去的是可选的第一行和最后一行。它会先 trim 掉这两行两边的空白,再比较第一行是否等于“ Begin Patch”、最后一行是否等于“ End Patch”。都对就返回成功;第一行不对就返回“第一行必须是...”的错误;否则返回“最后一行必须是...”的错误。

调用关系:check_patch_boundaries_strict 调用它完成最终判断。它是边界检查链条里最底层的小判官,只关心首尾两行,不解析补丁内容。

调用图:被 1 处调用(check_patch_boundaries_strict);外部调用 1 个(from)。

test_parse_patch261–408 ↗
fn test_parse_patch()

作用:验证普通补丁解析的主要情况都能按预期工作。它覆盖错误开头、错误结尾、新增文件、空更新、多个动作、更新后接新增、以及没有 @@ 标题的更新块。

数据流:进去的是测试里写死的多段补丁字符串。测试调用解析函数后,把得到的 Hunk 列表或错误结果与预期值比较。出来没有业务返回值;如果实际结果不符合预期,测试会失败。

调用关系:它通过 assert_eq! 检查结果,是给 parse_patch_text 和整体解析规则兜底的测试。它不被生产代码调用,只在测试运行时执行。

调用图:外部调用 1 个(assert_eq!)。

test_parse_patch_preserves_end_of_file_marker411–432 ↗
fn test_parse_patch_preserves_end_of_file_marker()

作用:确认“*** End of File”这个标记不会被误当成普通文字或丢掉。这个标记表示改动必须发生在文件末尾附近。

数据流:进去的是一段包含 End of File 标记的更新补丁。测试调用 parse_patch,并检查输出的 UpdateFileChunk 里 is_end_of_file 是 true,同时新内容和原始 patch 字符串都被正确保留。测试通过则没有返回值;不一致就失败。

调用关系:它直接测试 parse_patch。这个测试保护了文件末尾插入/替换这类场景,避免解析器把重要位置提示弄丢。

调用图:外部调用 1 个(assert_eq!)。

test_parse_patch_accepts_relative_and_absolute_hunk_paths435–477 ↗
fn test_parse_patch_accepts_relative_and_absolute_hunk_paths()

作用:确认补丁里的文件路径既可以是相对路径,也可以是绝对路径。这样调用方不用被迫统一成一种路径写法。

数据流:进去的是测试临时目录生成的绝对路径,以及手写的相对路径。测试把这些路径放进新增、删除、更新三种补丁动作里,调用 parse_patch_text 后检查解析出的路径是否原样正确。测试本身不改真实目标文件,只生成临时路径并比较结果。

调用关系:它调用 tempfile 创建临时目录,用 format! 拼补丁文本,再用 assert_eq! 验证 parse_patch_text 的输出。它保证解析阶段不会错误拒绝绝对路径。

调用图:外部调用 3 个(assert_eq!, format!, tempdir)。

test_hunk_resolve_path_accepts_relative_and_absolute_paths480–534 ↗
fn test_hunk_resolve_path_accepts_relative_and_absolute_paths()

作用:验证 Hunk::resolve_path 对相对路径和绝对路径的处理都正确。相对路径应该接到当前工作目录下面,绝对路径不该被乱改。

数据流:进去的是一组手工构造的 Hunk:有新增、删除、更新,也有相对路径和绝对路径。测试给出一个 cwd,然后逐个调用 resolve_path,对比结果是否等于预期的绝对路径。测试只比较路径,不创建或修改这些文件。

调用关系:它是 Hunk::resolve_path 的专门测试。通过临时目录和断言,确认路径解析这个基础动作可靠,防止后续真正应用补丁时找错文件。

调用图:外部调用 5 个(from, new, new, assert_eq!, tempdir)。

test_parse_patch_lenient537–623 ↗
fn test_parse_patch_lenient()

作用:验证宽松模式能识别并剥掉 heredoc 外壳,同时严格模式仍然会拒绝这种外壳。它也确认写错引号或缺少真正结束标记时不会被误放行。

数据流:进去的是同一段真实补丁,以及几种包在 <<EOF、<<'EOF'、<<"EOF" 外面的版本。测试分别用 Strict 和 Lenient 调 parse_patch_text,检查严格模式报错、宽松模式成功;对引号不匹配和缺失 End Patch 的情况,则检查仍然报错。出来是测试通过或失败。

调用关系:它围绕 check_patch_boundaries_lenient 的行为做验证,也间接测试 parse_patch_text 的模式选择。这个测试确保“宽松”只是兼容常见包装,不会无限制接受坏输入。

调用图:外部调用 3 个(assert_eq!, format!, vec!)。

test_parse_patch_environment_id_preamble626–660 ↗
fn test_parse_patch_environment_id_preamble()

作用:验证补丁开头可以带环境 ID,并且空的环境 ID 会被拒绝。环境 ID 可以理解成告诉工具“这份补丁针对哪个运行环境”的标签。

数据流:进去的是两段补丁:一段带有效的 Environment ID,一段 ID 只有空白。测试调用 parse_patch_text 后,检查有效版本能解析出 environment_id 和新增文件动作;空白版本会返回对应错误。测试不改文件,只验证解析结果。

调用关系:它测试 parse_patch_text 和底层流式解析器对环境 ID 前置行的处理。它保证环境标签既能被保留下来,也不会允许空标签混进后续流程。

调用图:外部调用 1 个(assert_eq!)。

apply-patch/src/invocation.rs源码 ↗
orchestrationrequest handling

这个文件解决的是一个很实际的问题:用户可能直接运行 apply_patch,也可能把补丁塞在 bash、PowerShell 或 cmd 的脚本里。如果系统误判了命令,可能把普通命令当补丁,或者在错误目录里改错文件。它先看命令行参数,判断 shell 类型;如果是 heredoc(脚本里用一段文本当输入,像把一封信夹在命令后面),就用 Tree-sitter(把脚本文字拆成语法树的工具)严格找出 apply_patch 和补丁正文。然后它解析补丁,计算真正会新增、删除、更新哪些文件;更新文件时还会读取原文件,生成统一 diff(常见的改动对比文本)。它特别保守:只接受简单明确的 apply_patch 形式,像前后夹了别的命令、cd 用错连接符、apply_patch 多带参数,都会拒绝,避免误操作。

函数细节51
classify_shell_name54–59 ↗
fn classify_shell_name(shell: &str) -> Option<String>

作用:把 shell 程序名整理成好比较的名字。比如传进来的是 /bin/bashpowershell.exe,它只取核心名字并转成小写。

数据流:进去的是一个 shell 路径字符串 → 它取文件名主干,去掉目录和扩展名,再转成小写 → 出来的是可比较的名字,或者取不到时返回空。

调用关系:它是后面判断 shell 类型的基础小工具。classify_shell 和 can_skip_flag 都先靠它把名字标准化,再决定该怎么解析参数。

调用图:被 2 处调用(can_skip_flag, classify_shell);外部调用 1 个(new)。

classify_shell61–70 ↗
fn classify_shell(shell: &str, flag: &str) -> Option<ApplyPatchShell>

作用:判断这组“shell 名字 + 启动参数”是不是系统认识的脚本运行方式。它会区分 Unix shell、PowerShell 和 Windows cmd。

数据流:进去的是 shell 名和一个参数,比如 bash-lc → 它先标准化 shell 名,再检查参数是否匹配 → 出来的是具体 shell 类型,或表示不认识。

调用关系:parse_shell_script 调它来确认命令行是不是类似 bash -lc 脚本 这种可解析形式。它依赖 classify_shell_name 做名字清洗。

调用图:调用 1 个内部函数(classify_shell_name);被 1 处调用(parse_shell_script)。

can_skip_flag72–76 ↗
fn can_skip_flag(shell: &str, flag: &str) -> bool

作用:判断某个额外参数能不能跳过。主要是 PowerShell 常见的 -NoProfile,意思是不加载用户配置,对补丁解析本身没影响。

数据流:进去的是 shell 名和一个参数 → 它确认 shell 是 PowerShell 系列,并且参数是 -NoProfile → 出来是真或假。

调用关系:parse_shell_script 在处理四段参数时会用它。这样 powershell -NoProfile -Command 脚本 也能被当作正常的 PowerShell 命令识别。

调用图:调用 1 个内部函数(classify_shell_name);被 1 处调用(parse_shell_script)。

parse_shell_script78–92 ↗
fn parse_shell_script(argv: &[String]) -> Option<(ApplyPatchShell, &str)>

作用:从命令行参数里找出“这是某种 shell 在执行一段脚本”这一模式,并取出脚本文字。

数据流:进去的是整个 argv 参数列表 → 它匹配三段或四段 shell 调用形式,必要时跳过 -NoProfile → 出来的是 shell 类型和脚本内容,或者表示不是这种形式。

调用关系:maybe_parse_apply_patch 和 maybe_parse_apply_patch_verified 都会先用它判断命令是不是 shell 包了一层。它把 shell 分类的活交给 classify_shell 和 can_skip_flag。

调用图:调用 2 个内部函数(can_skip_flag, classify_shell);被 2 处调用(maybe_parse_apply_patch, maybe_parse_apply_patch_verified)。

extract_apply_patch_from_shell94–103 ↗
fn extract_apply_patch_from_shell(
    shell: ApplyPatchShell,
    script: &str,
) -> std::result::Result<(String, Option<String>), ExtractHeredocError>

作用:根据 shell 类型,从脚本里抽出 apply_patch 的补丁正文和可选工作目录。

数据流:进去的是 shell 类型和脚本文字 → 当前所有支持的 shell 都走同一个 bash 风格 heredoc 提取逻辑 → 出来的是补丁正文和可能的 cd 路径,或者提取错误。

调用关系:maybe_parse_apply_patch 在确认是 shell 脚本后调用它。它把真正识别脚本结构的细活交给 extract_apply_patch_from_bash。

调用图:调用 1 个内部函数(extract_apply_patch_from_bash);被 1 处调用(maybe_parse_apply_patch)。

maybe_parse_apply_patch106–131 ↗
fn maybe_parse_apply_patch(argv: &[String]) -> MaybeApplyPatch

作用:判断一次命令是不是 apply_patch 调用;如果是,就把补丁解析成结构化数据。它只做“识别和解析”,还不检查文件系统上的真实内容。

数据流:进去的是 argv 参数列表 → 它先处理直接 apply_patch 补丁,否则尝试从 shell heredoc 中提取补丁,再调用 parse_patch 解析 → 出来的是补丁参数、补丁语法错误、shell 提取错误,或“不是 apply_patch”。

调用关系:这是文件里的第一道识别关口。maybe_parse_apply_patch_verified 会在更严格检查前调用它;测试里的多种命令形式也围绕它验证。

调用图:调用 3 个内部函数(extract_apply_patch_from_shell, parse_shell_script, parse_patch);被 5 处调用(maybe_parse_apply_patch_verified, assert_match_args, test_heredoc_applypatch, test_literal, test_literal_applypatch);外部调用 3 个(Body, PatchParseError, ShellParseError)。

maybe_parse_apply_patch_verified135–160 ↗
async fn maybe_parse_apply_patch_verified(
    argv: &[String],
    cwd: &AbsolutePathBuf,
    fs: &dyn ExecutorFileSystem,
    sandbox: Option<&codex_exec_server::FileSystemSandboxContext>,
) -> Mayb

作用:这是更安全的入口:不仅识别 apply_patch,还会检查这种调用是否合法,并把相对路径按当前目录算准。

数据流:进去的是 argv、当前绝对目录、文件系统接口和可选沙箱限制 → 它先拒绝“只传补丁正文但没写 apply_patch”的隐式调用,再解析命令,最后验证补丁涉及的文件 → 出来的是可执行的补丁动作、明确错误,或“不是补丁命令”。

调用关系:它位于真正执行补丁前的安全检查阶段。它先调用 parse_shell_script、parse_patch 和 maybe_parse_apply_patch,识别成功后把验证工作交给 verify_apply_patch_args。

调用图:调用 4 个内部函数(maybe_parse_apply_patch, parse_shell_script, verify_apply_patch_args, parse_patch);被 4 处调用(test_apply_patch_resolves_move_path_with_effective_cwd, test_apply_patch_should_resolve_absolute_paths_in_cwd, test_delete_symlink_still_verifies, test_unreadable_destinations_still_verify);外部调用 2 个(CorrectnessError, ShellParseError)。

verify_apply_patch_args162–235 ↗
async fn verify_apply_patch_args(
    args: ApplyPatchArgs,
    cwd: &AbsolutePathBuf,
    fs: &dyn ExecutorFileSystem,
    sandbox: Option<&codex_exec_server::FileSystemSandboxContext>,
) -> MaybeApp

作用:把已经解析好的补丁变成一份“确认过的改动清单”。它会读旧文件、算新内容和 diff,保证后面知道到底要改什么。

数据流:进去的是补丁参数、当前目录、文件系统接口和沙箱 → 它算出实际工作目录,逐个处理新增、删除、更新;删除会读取原内容,更新会生成统一 diff 和新内容 → 出来的是 ApplyPatchAction,或读文件、算 diff 时的正确性错误。

调用关系:maybe_parse_apply_patch_verified 在识别出补丁后调用它。它会使用文件系统读文件,也会把更新类补丁交给 unified_diff_from_chunks 生成改动对比。

调用图:调用 2 个内部函数(read_file_text, from_abs_path);被 1 处调用(maybe_parse_apply_patch_verified);外部调用 6 个(new, IoError, Body, CorrectnessError, unified_diff_from_chunks, format!)。

extract_apply_patch_from_bash257–387 ↗
fn extract_apply_patch_from_bash(
    src: &str,
) -> std::result::Result<(String, Option<String>), ExtractHeredocError>

作用:从一段脚本里严格找出允许的 apply_patch heredoc 形式。它只接受非常简单、明确的脚本,避免把复杂命令误当补丁。

数据流:进去的是脚本文字 → 它用 Tree-sitter Bash 语法解析器把脚本拆成语法树,再用查询规则找 apply_patch <<EOFcd 路径 && apply_patch <<EOF → 出来的是补丁正文和可选 cd 路径,或者说明没匹配、解析失败、文本不是 UTF-8。

调用关系:extract_apply_patch_from_shell 把具体提取任务交给它。它是 heredoc 识别的核心,决定哪些 shell 包装形式能进入补丁解析流程。

调用图:被 1 处调用(extract_apply_patch_from_shell);外部调用 3 个(new, new, new)。

tests::wrap_patch403–405 ↗
fn wrap_patch(body: &str) -> String

作用:给测试里的补丁片段自动包上标准开头和结尾,少写重复文字。

数据流:进去的是补丁中间内容 → 它前面加 *** Begin Patch,后面加 *** End Patch → 出来的是一段完整补丁文本。

调用关系:多个测试在构造更新文件的补丁时调用它,方便专注测试具体改动,而不是每次手写外壳。

调用图:外部调用 1 个(format!)。

tests::strs_to_strings407–409 ↗
fn strs_to_strings(strs: &[&str]) -> Vec<String>

作用:把测试里简单的字符串切片转成命令行参数需要的 String 列表。

数据流:进去的是若干 &str → 它逐个复制成拥有所有权的 String → 出来的是 Vec<String>。

调用关系:args_bash、args_powershell 等测试辅助函数都用它来生成 argv,直接调用 maybe_parse_apply_patch 的测试也会用到。

tests::args_bash412–414 ↗
fn args_bash(script: &str) -> Vec<String>

作用:快速造出 bash -lc 脚本 这种测试参数。

数据流:进去的是脚本文字 → 它和 bash-lc 拼成参数数组 → 出来的是 Vec<String>。

调用关系:很多 heredoc 相关测试通过它构造 bash 调用,然后交给 assert_match 或 assert_not_match。

调用图:外部调用 1 个(strs_to_strings)。

tests::args_powershell416–418 ↗
fn args_powershell(script: &str) -> Vec<String>

作用:快速造出 PowerShell 执行脚本的测试参数。

数据流:进去的是脚本文字 → 它拼成 powershell.exe -Command 脚本 → 出来的是 Vec<String>。

调用关系:PowerShell heredoc 测试用它确认 parse_shell_script 能识别 PowerShell 形式。

调用图:外部调用 1 个(strs_to_strings)。

tests::args_powershell_no_profile420–422 ↗
fn args_powershell_no_profile(script: &str) -> Vec<String>

作用:快速造出带 -NoProfile 的 PowerShell 测试参数。

数据流:进去的是脚本文字 → 它拼成 powershell.exe -NoProfile -Command 脚本 → 出来的是 Vec<String>。

调用关系:专门用于验证 can_skip_flag 能跳过 PowerShell 的无关启动参数。

调用图:外部调用 1 个(strs_to_strings)。

tests::args_pwsh424–426 ↗
fn args_pwsh(script: &str) -> Vec<String>

作用:快速造出现代 PowerShell pwsh 的测试参数。

数据流:进去的是脚本文字 → 它拼成 pwsh -NoProfile -Command 脚本 → 出来的是 Vec<String>。

调用关系:pwsh 相关测试用它覆盖另一种 PowerShell 可执行文件名。

调用图:外部调用 1 个(strs_to_strings)。

tests::args_cmd428–430 ↗
fn args_cmd(script: &str) -> Vec<String>

作用:快速造出 Windows cmd 执行脚本的测试参数。

数据流:进去的是脚本文字 → 它拼成 cmd.exe /c 脚本 → 出来的是 Vec<String>。

调用关系:cmd heredoc 测试用它确认 Windows cmd 形式也能被识别,并最终走同一套提取逻辑。

调用图:外部调用 1 个(strs_to_strings)。

tests::heredoc_script432–436 ↗
fn heredoc_script(prefix: &str) -> String

作用:生成一段包含 apply_patch heredoc 的测试脚本,可在前面加不同前缀。

数据流:进去的是脚本前缀,比如空字符串或 cd foo && → 它拼出完整 apply_patch heredoc,里面是新增 foo 文件的补丁 → 出来的是脚本文字。

调用关系:大量正反向测试都用它制造输入,再交给 assert_match 或 assert_not_match。

调用图:外部调用 1 个(format!)。

tests::heredoc_script_ps438–442 ↗
fn heredoc_script_ps(prefix: &str, suffix: &str) -> String

作用:生成一段 heredoc 测试脚本,并允许同时加前缀和后缀。

数据流:进去的是前缀和后缀 → 它把 apply_patch heredoc 夹在中间 → 出来的是脚本文字。

调用关系:用于测试“apply_patch 后面又接了额外命令”这种应该拒绝的情况。

调用图:外部调用 1 个(format!)。

tests::expected_single_add444–449 ↗
fn expected_single_add() -> Vec<Hunk>

作用:给测试提供一个标准答案:补丁应该表示新增名为 foo 的文件,内容是 hi。

数据流:进去没有参数 → 它构造一个 AddFile 类型的 hunk → 出来的是只含这一项的列表。

调用关系:assert_match_args 用它比较解析结果,避免每个测试重复写期望结构。

调用图:外部调用 1 个(vec!)。

tests::assert_match_args451–459 ↗
fn assert_match_args(args: Vec<String>, expected_workdir: Option<&str>)

作用:检查某组命令参数确实会被识别成 apply_patch,并且工作目录符合预期。

数据流:进去的是 argv 和期望 workdir → 它调用 maybe_parse_apply_patch,取出 hunks 和 workdir 与标准答案比较 → 成功就无输出,失败就让测试崩掉。

调用关系:许多 shell 形式测试都通过它做统一断言。它直接检验 maybe_parse_apply_patch 的结果。

调用图:调用 1 个内部函数(maybe_parse_apply_patch);外部调用 2 个(assert_eq!, panic!)。

tests::assert_match461–464 ↗
fn assert_match(script: &str, expected_workdir: Option<&str>)

作用:检查一段 bash 脚本能成功匹配 apply_patch heredoc。

数据流:进去的是脚本文字和期望 workdir → 它先用 args_bash 包成 bash -lc 参数,再调用 assert_match_args → 成功表示脚本被正确识别。

调用关系:这是 bash 正向测试的简化入口,把构造参数和断言两个步骤串起来。

调用图:外部调用 2 个(args_bash, assert_match_args)。

tests::assert_not_match466–472 ↗
fn assert_not_match(script: &str)

作用:检查一段 bash 脚本不应该被当成 apply_patch。

数据流:进去的是脚本文字 → 它包成 bash 参数,调用 maybe_parse_apply_patch → 期望结果是 NotApplyPatch,否则测试失败。

调用关系:各种危险或复杂脚本形式的反向测试都用它,确保 extract_apply_patch_from_bash 足够保守。

调用图:外部调用 2 个(args_bash, assert_matches!)。

tests::test_implicit_patch_single_arg_is_error475–489 ↗
async fn test_implicit_patch_single_arg_is_error()

作用:确认如果只传一段补丁正文、没显式写 apply_patch,会被当作错误而不是偷偷执行。

数据流:进去没有外部输入 → 测试构造单参数补丁和临时目录,调用 maybe_parse_apply_patch_verified → 期望得到 ImplicitInvocation 错误。

调用关系:它覆盖 maybe_parse_apply_patch_verified 开头的安全检查,防止隐式补丁调用绕过正常命令格式。

调用图:外部调用 3 个(assert_matches!, tempdir, vec!)。

tests::test_implicit_patch_bash_script_is_error492–506 ↗
async fn test_implicit_patch_bash_script_is_error()

作用:确认 shell 脚本内容如果本身就是裸补丁,也会被明确拒绝。

数据流:进去没有外部输入 → 测试把补丁正文放进 bash 参数,调用 maybe_parse_apply_patch_verified → 期望得到隐式调用错误。

调用关系:它验证 maybe_parse_apply_patch_verified 对 shell 脚本里的裸补丁也会先拦截,而不是误解析执行。

调用图:外部调用 3 个(args_bash, assert_matches!, tempdir)。

tests::test_literal509–531 ↗
async fn test_literal()

作用:确认直接写 apply_patch 补丁正文 能被解析成功。

数据流:进去没有外部输入 → 测试构造 apply_patch argv,调用 maybe_parse_apply_patch → 期望解析出新增 foo 文件的 hunk。

调用关系:它覆盖 maybe_parse_apply_patch 的直接调用分支,是最基本的正向测试。

调用图:调用 1 个内部函数(maybe_parse_apply_patch);外部调用 3 个(strs_to_strings, assert_eq!, panic!)。

tests::test_literal_applypatch534–556 ↗
async fn test_literal_applypatch()

作用:确认命令别名 applypatch 也能像 apply_patch 一样工作。

数据流:进去没有外部输入 → 测试构造 applypatch argv,调用 maybe_parse_apply_patch → 期望得到同样的新增文件结果。

调用关系:它验证 APPLY_PATCH_COMMANDS 里的第二个命令名确实被 maybe_parse_apply_patch 接受。

调用图:调用 1 个内部函数(maybe_parse_apply_patch);外部调用 3 个(strs_to_strings, assert_eq!, panic!)。

tests::test_heredoc559–561 ↗
async fn test_heredoc()

作用:确认最普通的 bash heredoc 写法能被识别。

数据流:进去没有外部输入 → 它生成无前缀 heredoc 脚本并断言匹配 → 成功表示补丁正文能从脚本里抽出。

调用关系:它通过 assert_match 间接测试 parse_shell_script、extract_apply_patch_from_shell 和 extract_apply_patch_from_bash 的联动。

调用图:外部调用 2 个(assert_match, heredoc_script)。

tests::test_heredoc_non_login_shell564–568 ↗
async fn test_heredoc_non_login_shell()

作用:确认 bash 用 -c 而不是 -lc 时也能识别 heredoc。

数据流:进去没有外部输入 → 它生成脚本,构造 bash -c argv,调用 assert_match_args → 期望正常匹配。

调用关系:它验证 classify_shell 对 Unix shell 参数的接受范围。

调用图:外部调用 3 个(assert_match_args, heredoc_script, strs_to_strings)。

tests::test_heredoc_applypatch571–596 ↗
async fn test_heredoc_applypatch()

作用:确认 heredoc 里使用 applypatch 别名也能被识别。

数据流:进去没有外部输入 → 它构造 bash heredoc 脚本,命令名是 applypatch,调用 maybe_parse_apply_patch → 期望得到新增 foo 的 hunk。

调用关系:它直接覆盖 extract_apply_patch_from_bash 查询规则里的命令名白名单。

调用图:调用 1 个内部函数(maybe_parse_apply_patch);外部调用 3 个(strs_to_strings, assert_eq!, panic!)。

tests::test_powershell_heredoc599–602 ↗
async fn test_powershell_heredoc()

作用:确认 PowerShell 包装的 heredoc 形式能被识别。

数据流:进去没有外部输入 → 它生成 heredoc 脚本,用 args_powershell 包装,再断言匹配 → 成功表示 PowerShell 参数分类没问题。

调用关系:它主要测试 parse_shell_script 和 classify_shell 对 powershell.exe -Command 的处理。

调用图:外部调用 3 个(args_powershell, assert_match_args, heredoc_script)。

tests::test_powershell_heredoc_no_profile604–610 ↗
async fn test_powershell_heredoc_no_profile()

作用:确认 PowerShell 多带 -NoProfile 时仍然能识别补丁。

数据流:进去没有外部输入 → 它构造带 -NoProfile 的 PowerShell argv 并断言匹配 → 成功表示这个额外参数被正确跳过。

调用关系:它覆盖 can_skip_flag 和 parse_shell_script 的四参数分支。

调用图:外部调用 3 个(args_powershell_no_profile, assert_match_args, heredoc_script)。

tests::test_pwsh_heredoc612–615 ↗
async fn test_pwsh_heredoc()

作用:确认 pwsh 这个 PowerShell 新名字也支持 heredoc 补丁识别。

数据流:进去没有外部输入 → 它生成脚本,用 args_pwsh 包装,再断言匹配 → 成功表示 pwsh 被归类为 PowerShell。

调用关系:它验证 classify_shell_name 和 classify_shell 对不同 PowerShell 可执行名的兼容。

调用图:外部调用 3 个(args_pwsh, assert_match_args, heredoc_script)。

tests::test_cmd_heredoc_with_cd618–621 ↗
async fn test_cmd_heredoc_with_cd()

作用:确认 Windows cmd 包装并带 cd foo && 时,能识别补丁和工作目录。

数据流:进去没有外部输入 → 它生成带 cd 前缀的脚本,用 cmd 参数包装,断言 workdir 是 foo → 成功表示 cd 路径被提取出来。

调用关系:它测试 parse_shell_script 对 cmd 的识别,以及 extract_apply_patch_from_bash 对 cd && apply_patch 形态的解析。

调用图:外部调用 3 个(args_cmd, assert_match_args, heredoc_script)。

tests::test_heredoc_with_leading_cd624–626 ↗
async fn test_heredoc_with_leading_cd()

作用:确认 bash 脚本里 cd foo && apply_patch 会把 foo 当作补丁工作目录。

数据流:进去没有外部输入 → 它生成带 cd foo && 的 heredoc 脚本并断言匹配 workdir → 成功表示工作目录提取正确。

调用关系:它覆盖 extract_apply_patch_from_bash 的第二种允许形式。

调用图:外部调用 2 个(assert_match, heredoc_script)。

tests::test_cd_with_semicolon_is_ignored629–631 ↗
async fn test_cd_with_semicolon_is_ignored()

作用:确认 cd foo; apply_patch 这种用分号连接的脚本不会被接受。

数据流:进去没有外部输入 → 它生成分号形式脚本并断言不匹配 → 成功表示只有 && 连接才允许。

调用关系:它保护 extract_apply_patch_from_bash 的严格规则,防止工作目录切换失败后还继续补丁。

调用图:外部调用 2 个(assert_not_match, heredoc_script)。

tests::test_cd_or_apply_patch_is_ignored634–636 ↗
async fn test_cd_or_apply_patch_is_ignored()

作用:确认 cd bar || apply_patch 这种“失败才执行”的形式不会被接受。

数据流:进去没有外部输入 → 它生成 || 连接脚本并断言不匹配 → 成功表示危险连接符被拒绝。

调用关系:它验证 extract_apply_patch_from_bash 只接受 &&,不接受逻辑或。

调用图:外部调用 2 个(assert_not_match, heredoc_script)。

tests::test_cd_pipe_apply_patch_is_ignored639–641 ↗
async fn test_cd_pipe_apply_patch_is_ignored()

作用:确认 cd bar | apply_patch 这种管道形式不会被当成补丁调用。

数据流:进去没有外部输入 → 它生成管道脚本并断言不匹配 → 成功表示复杂 shell 组合被拒绝。

调用关系:它继续验证 extract_apply_patch_from_bash 的保守匹配策略。

调用图:外部调用 2 个(assert_not_match, heredoc_script)。

tests::test_cd_single_quoted_path_with_spaces644–646 ↗
async fn test_cd_single_quoted_path_with_spaces()

作用:确认单引号包住、带空格的目录名能被正确提取。

数据流:进去没有外部输入 → 它生成 cd 'foo bar' && apply_patch 脚本 → 期望 workdir 是 foo bar

调用关系:它覆盖 extract_apply_patch_from_bash 对 raw string 形式 cd 参数的处理。

调用图:外部调用 2 个(assert_match, heredoc_script)。

tests::test_cd_double_quoted_path_with_spaces649–651 ↗
async fn test_cd_double_quoted_path_with_spaces()

作用:确认双引号包住、带空格的目录名能被正确提取。

数据流:进去没有外部输入 → 它生成 cd "foo bar" && apply_patch 脚本 → 期望 workdir 是 foo bar

调用关系:它验证 Tree-sitter 查询能从双引号字符串里抓到 cd 路径内容。

调用图:外部调用 2 个(assert_match, heredoc_script)。

tests::test_echo_and_apply_patch_is_ignored654–656 ↗
async fn test_echo_and_apply_patch_is_ignored()

作用:确认 echo foo && apply_patch 不会被误当作带工作目录的补丁命令。

数据流:进去没有外部输入 → 它生成 echo 前缀脚本并断言不匹配 → 成功表示前置命令必须是 cd。

调用关系:它测试 extract_apply_patch_from_bash 对命令名 cd 的严格要求。

调用图:外部调用 2 个(assert_not_match, heredoc_script)。

tests::test_apply_patch_with_arg_is_ignored659–662 ↗
async fn test_apply_patch_with_arg_is_ignored()

作用:确认 heredoc 形式里的 apply_patch 不能额外带普通参数。

数据流:进去没有外部输入 → 它构造 apply_patch foo <<PATCH 脚本并断言不匹配 → 成功表示只接受干净的 apply_patch 命令。

调用关系:它防止 extract_apply_patch_from_bash 接受含糊的调用形式。

调用图:外部调用 1 个(assert_not_match)。

tests::test_double_cd_then_apply_patch_is_ignored665–667 ↗
async fn test_double_cd_then_apply_patch_is_ignored()

作用:确认连续两个 cd 再 apply_patch 的复杂形式不会被接受。

数据流:进去没有外部输入 → 它生成 cd foo && cd bar && apply_patch 脚本并断言不匹配 → 成功表示只允许一个简单 cd。

调用关系:它测试 extract_apply_patch_from_bash 的顶层结构限制。

调用图:外部调用 2 个(assert_not_match, heredoc_script)。

tests::test_cd_two_args_is_ignored670–672 ↗
async fn test_cd_two_args_is_ignored()

作用:确认 cd 带两个参数时不会被接受。

数据流:进去没有外部输入 → 它生成 cd foo bar && apply_patch 脚本并断言不匹配 → 成功表示 cd 必须只有一个路径参数。

调用关系:它覆盖 extract_apply_patch_from_bash 查询里对 cd 参数数量的限制。

调用图:外部调用 2 个(assert_not_match, heredoc_script)。

tests::test_cd_then_apply_patch_then_extra_is_ignored675–678 ↗
async fn test_cd_then_apply_patch_then_extra_is_ignored()

作用:确认 apply_patch heredoc 后面再接额外命令时不会被接受。

数据流:进去没有外部输入 → 它生成带后缀 && echo done 的脚本并断言不匹配 → 成功表示整个脚本只能是这一次补丁调用。

调用关系:它验证 extract_apply_patch_from_bash 使用首尾锚点限制顶层语句,避免夹带后续命令。

调用图:外部调用 2 个(assert_not_match, heredoc_script_ps)。

tests::test_echo_then_cd_and_apply_patch_is_ignored681–684 ↗
async fn test_echo_then_cd_and_apply_patch_is_ignored()

作用:确认补丁命令前面还有别的命令时不会被接受。

数据流:进去没有外部输入 → 它生成 echo foo; cd bar && apply_patch 脚本并断言不匹配 → 成功表示前置命令会导致拒绝。

调用关系:它验证 extract_apply_patch_from_bash 要求 apply_patch 相关语句是唯一顶层语句。

调用图:外部调用 2 个(assert_not_match, heredoc_script)。

tests::test_unified_diff_last_line_replacement687–726 ↗
async fn test_unified_diff_last_line_replacement()

作用:确认替换文件最后一行时,生成的统一 diff 是正确的。

数据流:进去没有外部输入 → 它创建临时文件,构造替换最后一行的补丁,解析后调用 unified_diff_from_chunks → 期望 diff、原内容、新内容都匹配。

调用关系:它虽然在本文件测试区,但主要保护 verify_apply_patch_args 依赖的 diff 生成行为,尤其是文件末尾边界情况。

调用图:调用 1 个内部函数(parse_patch);外部调用 7 个(wrap_patch, assert_eq!, unified_diff_from_chunks, format!, write, panic!, tempdir)。

tests::test_unified_diff_insert_at_eof729–765 ↗
async fn test_unified_diff_insert_at_eof()

作用:确认在文件末尾追加一行时,统一 diff 能正确表示这个插入。

数据流:进去没有外部输入 → 它创建临时文件,构造 End of File 插入补丁,解析并生成 diff → 期望新内容末尾多出一行。

调用关系:它覆盖 unified_diff_from_chunks 的另一个末尾边界,也间接保证 verify_apply_patch_args 处理更新补丁时可靠。

调用图:调用 1 个内部函数(parse_patch);外部调用 7 个(wrap_patch, assert_eq!, unified_diff_from_chunks, format!, write, panic!, tempdir)。

tests::test_apply_patch_should_resolve_absolute_paths_in_cwd768–817 ↗
async fn test_apply_patch_should_resolve_absolute_paths_in_cwd()

作用:确认相对路径会按传入的当前目录来解析,而不是按进程碰巧所在目录。

数据流:进去没有外部输入 → 它在临时目录建文件,用相对路径写补丁,调用 maybe_parse_apply_patch_verified → 期望改动清单里的路径是临时目录下的绝对路径。

调用关系:它直接测试 maybe_parse_apply_patch_verified 和 verify_apply_patch_args 对 cwd 的使用,防止改错目录里的同名文件。

调用图:调用 2 个内部函数(maybe_parse_apply_patch_verified, from_absolute_path);外部调用 4 个(assert_eq!, write, tempdir, vec!)。

tests::test_apply_patch_resolves_move_path_with_effective_cwd820–871 ↗
async fn test_apply_patch_resolves_move_path_with_effective_cwd()

作用:确认带 cd 的补丁里,移动目标路径也会按切换后的工作目录来解析。

数据流:进去没有外部输入 → 它建一个子目录和源文件,构造 cd alt && apply_patch 的移动更新补丁,调用 maybe_parse_apply_patch_verified → 期望 action.cwd 和 move_path 都指向子目录下的位置。

调用关系:它验证 extract_apply_patch_from_bash 提取的 workdir 会传到 verify_apply_patch_args,并影响普通路径和移动目标路径。

调用图:调用 2 个内部函数(maybe_parse_apply_patch_verified, from_absolute_path);外部调用 8 个(wrap_patch, assert_eq!, format!, create_dir_all, write, panic!, tempdir, vec!)。

tests::test_unreadable_destinations_still_verify874–899 ↗
async fn test_unreadable_destinations_still_verify()

作用:确认目标位置已有不可按文本读取的文件时,新增或移动到那里仍可通过验证。

数据流:进去没有外部输入 → 它创建一个二进制文件,再分别测试新增同名文件和移动到该文件名 → 期望 maybe_parse_apply_patch_verified 返回可执行的 Body。

调用关系:它保护 verify_apply_patch_args 的设计:验证新增或移动目标时不应强行读取目标内容,否则二进制文件会误报失败。

调用图:调用 2 个内部函数(maybe_parse_apply_patch_verified, from_absolute_path);外部调用 4 个(assert!, write, tempdir, vec!)。

补丁执行引擎

这些文件执行实际的补丁应用工作,可通过原生补丁引擎,或通过 git 支持的 unified-diff 应用完成。

apply-patch/src/lib.rs源码 ↗
domain_logicrequest handling

可以把它想成一个谨慎的施工队:先读懂施工单,也就是 patch;再确认每个文件该新增、删除、改内容还是搬家;然后通过统一的文件系统接口去动磁盘。它不只是“写文件”这么简单,还会处理相对路径、缺父目录时自动建目录、删除前确认目标不是文件夹、更新时先算出新内容。更重要的是,它会记录已经提交的改动 AppliedPatchDelta。这样如果中途失败,比如磁盘写到一半出错,调用方还能知道哪些改动已经发生,以及这份记录是否完全可靠。文件里也包含生成预览用的 unified diff,以及一批测试,覆盖新增、删除、移动、Unicode 标点匹配、写失败等边界情况。

函数细节49
ApplyPatchError::from70–75 ↗
fn from(err: &std::io::Error) -> Self

作用:把普通的输入输出错误包装成 apply_patch 自己认识的错误。这样上层不用猜错误来自哪里,可以统一处理。

数据流:进去的是一个系统 I/O 错误,也就是读写文件时出的错;它复制错误类型和文字说明,加上默认的“I/O error”上下文;出来的是 ApplyPatchError::IoError。

调用关系:apply_hunks 在写错误信息或转换底层错误时会用它,把外部文件系统错误接到本文件的错误体系里。

调用图:被 1 处调用(apply_hunks);外部调用 4 个(IoError, kind, new, to_string)。

IoError::eq87–89 ↗
fn eq(&self, other: &Self) -> bool

作用:判断两个 IoError 是否可以算作同一个错误。它主要服务测试和比较,不直接改文件。

数据流:进去的是两个 IoError;它比较上下文文字和底层错误转成字符串后的内容;出来是真或假。

调用关系:这是 IoError 的相等规则,配合 ApplyPatchError 的比较使用,让测试能稳定判断错误内容。

调用图:外部调用 1 个(to_string)。

ApplyPatchAction::is_empty149–151 ↗
fn is_empty(&self) -> bool

作用:判断解析出来的补丁动作是不是没有任何文件变化。调用方可以用它决定是否还需要继续审批或执行。

数据流:进去的是一个 ApplyPatchAction;它查看内部 changes 表是否为空;出来的是布尔值,表示有没有改动。

调用关系:assess_patch_safety 会调用它,像先看施工单是不是空白,再决定后续安全检查要不要做。

调用图:被 1 处调用(assess_patch_safety)。

ApplyPatchAction::changes154–156 ↗
fn changes(&self) -> &HashMap<PathBuf, ApplyPatchFileChange>

作用:把补丁会造成的文件变化拿出来给别人看,但不允许别人直接改这份记录。

数据流:进去的是 ApplyPatchAction;它返回内部 changes 的只读引用;出来的是路径到文件变化的映射。

调用关系:协议转换、安全路径检查、提取文件路径等流程会调用它,用同一份“将要改哪些文件”的清单做各自的判断。

调用图:被 3 处调用(convert_apply_patch_to_protocol, is_write_patch_constrained_to_writable_paths, file_paths_for_action)。

ApplyPatchAction::new_add_for_test160–180 ↗
fn new_add_for_test(path: &AbsolutePathBuf, content: String) -> Self

作用:专门给测试造一个“新增文件”的补丁动作。正常业务不该靠它构造真实请求。

数据流:进去的是绝对路径和文件内容;它拼出一段补丁文本,并把变化表填成新增这个文件;出来的是一个 ApplyPatchAction。

调用关系:多个安全审批相关测试会调用它,快速制造一个可控的补丁动作,不必每个测试都手写完整解析过程。

调用图:调用 2 个内部函数(parent, to_path_buf);被 14 处调用(convert_apply_patch_maps_add_variant, explicit_read_only_subpaths_prevent_auto_approval_for_external_sandbox, explicit_unreadable_paths_prevent_auto_approval_for_external_sandbox, external_sandbox_auto_approves_in_on_request, granular_sandbox_approval_false_rejects_out_of_root_patch, granular_with_all_flags_true_matches_on_request_for_out_of_root_patch, missing_project_dot_codex_config_requires_approval, read_only_policy_rejects_patch_with_read_only_reason, approval_keys_include_environment_id, file_system_sandbox_context_uses_active_attempt (+4 more));外部调用 3 个(from, format!, file_name)。

AppliedPatchDelta::new191–193 ↗
fn new(changes: Vec<AppliedPatchChange>, exact: bool) -> Self

作用:创建一份“已经实际发生的文件变化”记录。它同时记住变化列表和这份记录是否精确。

数据流:进去的是变化列表和 exact 标记;它原样放进结构体;出来的是 AppliedPatchDelta。

调用关系:这是 Delta 的基础构造器,empty、测试和失败记录都会间接或直接依赖它。

AppliedPatchDelta::empty195–197 ↗
fn empty() -> Self

作用:创建一份空的已应用变化记录,表示目前还没有文件被真正改动。

数据流:进去没有额外数据;它构造空列表,并把 exact 设为 true;出来的是空的 AppliedPatchDelta。

调用关系:apply_hunks 开始执行前会用它当账本,ApplyPatchFailure::without_delta 也用它表示失败发生在任何改动之前。

调用图:被 2 处调用(without_delta, apply_hunks);外部调用 2 个(new, new)。

AppliedPatchDelta::changes199–201 ↗
fn changes(&self) -> &[AppliedPatchChange]

作用:查看已经提交的文件变化清单。它让外部能读账本,但不能随便改账本。

数据流:进去的是 AppliedPatchDelta;它返回内部变化列表的只读切片;出来的是按发生顺序排列的变化。

调用关系:track_delta 会读取它,把实际发生的补丁变化同步到更上层的状态跟踪里。

调用图:被 1 处调用(track_delta)。

AppliedPatchDelta::is_empty203–205 ↗
fn is_empty(&self) -> bool

作用:判断这次补丁执行到目前为止有没有真正改过文件。

数据流:进去的是 AppliedPatchDelta;它检查 changes 列表是否为空;出来是真或假。

调用关系:tracker_update_for_known_delta 会用它判断是否需要更新外部追踪状态。

调用图:被 1 处调用(tracker_update_for_known_delta)。

AppliedPatchDelta::is_exact207–209 ↗
fn is_exact(&self) -> bool

作用:判断这份“已发生变化记录”是否完全可信。比如写文件失败可能已经截断文件,这时记录就可能不精确。

数据流:进去的是 AppliedPatchDelta;它读取 exact 标记;出来的是布尔值。

调用关系:状态跟踪和记录补丁结果时会调用它,决定能不能把这份 delta 当作准确事实使用。

调用图:被 2 处调用(tracker_update_for_known_delta, track_delta)。

AppliedPatchDelta::append212–215 ↗
fn append(&mut self, other: Self)

作用:把后发生的一段变化追加到当前记录后面。它像合并两页流水账,同时保留“不精确”的风险标记。

数据流:进去的是当前 delta 和另一个 delta;它把另一个的变化接到末尾,并把 exact 做合并,只要有一个不精确,结果就不精确;出来是被修改后的当前对象。

调用关系:run 会在分段执行补丁时调用它,把多段已经提交的变化合成一份总账。

调用图:被 1 处调用(run)。

AppliedPatchDelta::default219–221 ↗
fn default() -> Self

作用:提供默认的空变化记录。这样需要默认值的地方不用手动写构造细节。

数据流:进去没有数据;它调用 empty;出来的是空且精确的 AppliedPatchDelta。

调用关系:这是 Rust 默认值机制的一部分,让外部结构初始化时能自然拿到一份空账本。

调用图:外部调用 1 个(empty)。

ApplyPatchFailure::new259–261 ↗
fn new(error: ApplyPatchError, delta: AppliedPatchDelta) -> Self

作用:创建一个补丁失败对象,里面同时放错误原因和失败前已经发生的改动。

数据流:进去的是 ApplyPatchError 和 AppliedPatchDelta;它把二者打包;出来的是 ApplyPatchFailure。

调用关系:apply_hunks 在执行失败或连打印摘要都失败时会调用它,确保错误不是孤零零的,还带着现场记录。

调用图:被 1 处调用(apply_hunks)。

ApplyPatchFailure::without_delta263–265 ↗
fn without_delta(error: ApplyPatchError) -> Self

作用:创建一个“不带已改动记录”的失败对象,表示还没动文件就失败了。

数据流:进去的是错误;它生成空 delta,再调用 new 打包;出来的是 ApplyPatchFailure。

调用关系:apply_patch 在补丁解析失败、或者连错误信息都写不出去时会用它,因为这时还没有进入真正改文件阶段。

调用图:调用 1 个内部函数(empty);被 1 处调用(apply_patch);外部调用 1 个(new)。

ApplyPatchFailure::delta267–269 ↗
fn delta(&self) -> &AppliedPatchDelta

作用:让调用方查看失败前已经确认发生的文件变化。

数据流:进去的是失败对象;它返回内部 delta 的只读引用;出来的是已提交变化记录。

调用关系:测试和上层错误处理可以用它判断失败是否留下了部分改动,例如移动文件时目标已写好但源文件删除失败。

ApplyPatchFailure::into_parts271–273 ↗
fn into_parts(self) -> (ApplyPatchError, AppliedPatchDelta)

作用:把失败对象拆开,拿到错误本身和变化记录两个部分。

数据流:进去的是 ApplyPatchFailure 本身;它消费这个对象;出来的是 ApplyPatchError 和 AppliedPatchDelta。

调用关系:适合上层想分别处理“为什么失败”和“已经改了什么”的场景。

apply_patch277–313 ↗
async fn apply_patch(
    patch: &str,
    cwd: &AbsolutePathBuf,
    stdout: &mut impl std::io::Write,
    stderr: &mut impl std::io::Write,
    fs: &dyn ExecutorFileSystem,
    sandbox: Option<&File

作用:这是外部最常用的入口:给它一段 patch 文本,它负责解析、执行,并把成功或失败信息写到输出里。

数据流:进去的是补丁文本、工作目录、标准输出、标准错误、文件系统接口和可选沙箱;它先 parse_patch,解析失败就打印人能看懂的错误,解析成功就交给 apply_hunks;出来是已应用变化记录,或带 delta 的失败。

调用关系:测试直接大量调用它;真实命令路径也会把原始补丁交给它。它自己不逐个改文件,而是把解析好的 hunk 交给 apply_hunks。

调用图:调用 3 个内部函数(without_delta, apply_hunks, parse_patch);被 14 处调用(test_add_file_hunk_creates_file_with_contents, test_apply_patch_fails_on_write_error, test_apply_patch_hunks_accept_relative_and_absolute_paths, test_delete_file_hunk_removes_file, test_delete_symlink_returns_inexact_delta, test_failed_move_returns_committed_destination_delta, test_multiple_update_chunks_apply_to_single_file, test_pure_addition_chunk_followed_by_removal, test_unified_diff_interleaved_changes, test_unreadable_destinations_return_inexact_delta (+4 more));外部调用 2 个(ParseError, writeln!)。

apply_hunks316–348 ↗
async fn apply_hunks(
    hunks: &[Hunk],
    cwd: &AbsolutePathBuf,
    stdout: &mut impl std::io::Write,
    stderr: &mut impl std::io::Write,
    fs: &dyn ExecutorFileSystem,
    sandbox: Option<&F

作用:执行已经解析好的补丁块,并负责把最终结果写成用户能看到的成功摘要或错误信息。

数据流:进去的是 hunk 列表、工作目录、输出流、文件系统和沙箱;它创建空 delta,调用 apply_hunks_to_files 真正改文件,成功就 print_summary,失败就写错误并转换成 ApplyPatchFailure;出来是 delta 或失败。

调用关系:apply_patch 解析成功后会调用它。它是“解析”和“实际文件操作”之间的调度层。

调用图:调用 5 个内部函数(empty, from, new, apply_hunks_to_files, print_summary);被 1 处调用(apply_patch);外部调用 3 个(IoError, other, writeln!)。

apply_hunks_to_files362–552 ↗
async fn apply_hunks_to_files(
    hunks: &[Hunk],
    cwd: &AbsolutePathBuf,
    fs: &dyn ExecutorFileSystem,
    sandbox: Option<&FileSystemSandboxContext>,
    delta: &mut AppliedPatchDelta,
) -> a

作用:这是实际动文件的核心工人:按补丁要求新增、删除、修改或移动文件。

数据流:进去的是 hunk、当前目录、文件系统、沙箱和可写的 delta 账本;它逐个解析路径、读旧内容、算新内容、写文件或删除文件,并记录 added、modified、deleted;出来是 AffectedPaths,失败时返回错误且 delta 里保留已发生变化。

调用关系:apply_hunks 调用它。它会把细活分给 derive_new_contents_from_chunks、ensure_not_directory、read_optional_file_text_for_delta、write_file_with_missing_parent_retry 等辅助函数。

调用图:调用 8 个内部函数(derive_new_contents_from_chunks, ensure_not_directory, note_existing_path_delta_support, read_optional_file_text_for_delta, remove_failure_was_side_effect_free, read_file_text, resolve_path_against_base, from_abs_path);被 1 处调用(apply_hunks);外部调用 5 个(new, bail!, is_empty, remove, try_write!)。

ensure_not_directory554–568 ↗
async fn ensure_not_directory(
    path: &AbsolutePathBuf,
    fs: &dyn ExecutorFileSystem,
    sandbox: Option<&FileSystemSandboxContext>,
) -> io::Result<()>

作用:确认要删除的目标不是文件夹。这样可以避免把“删文件”的补丁误用到目录上。

数据流:进去的是绝对路径、文件系统和沙箱;它读取元数据,看目标是不是目录;出来是成功或一个 InvalidInput 类型的 I/O 错误。

调用关系:apply_hunks_to_files 在删除文件、或移动后删除原文件前调用它,像施工前确认目标不是整栋楼。

调用图:调用 1 个内部函数(from_abs_path);被 1 处调用(apply_hunks_to_files);外部调用 2 个(new, get_metadata)。

remove_failure_was_side_effect_free570–584 ↗
async fn remove_failure_was_side_effect_free(
    path: &AbsolutePathBuf,
    expected_content: Option<&str>,
    fs: &dyn ExecutorFileSystem,
    sandbox: Option<&FileSystemSandboxContext>,
) -> bool

作用:删除失败后,检查这个失败有没有留下副作用。简单说,就是看文件内容是不是还和删除前一样。

数据流:进去的是路径、预期内容、文件系统和沙箱;如果有预期内容,它重新读取文件并比较;出来是真或假,表示删除失败是否看起来没改坏东西。

调用关系:apply_hunks_to_files 在删除或移动源文件失败时调用它,用来决定 delta 的 exact 标记还能不能保持为真。

调用图:调用 2 个内部函数(read_file_text, from_abs_path);被 1 处调用(apply_hunks_to_files)。

read_optional_file_text_for_delta586–602 ↗
async fn read_optional_file_text_for_delta(
    path: &AbsolutePathBuf,
    fs: &dyn ExecutorFileSystem,
    sandbox: Option<&FileSystemSandboxContext>,
    exact: &mut bool,
) -> Option<String>

作用:尝试读取一个可能存在的文件内容,用来记录“覆盖前是什么”。读不到不存在的文件不算错。

数据流:进去的是路径、文件系统、沙箱和 exact 标记;它先检查路径类型是否适合精确记录,再读文本;出来是 Some 内容或 None,遇到非“不存在”的读取错误会把 exact 设为 false。

调用关系:apply_hunks_to_files 在新增文件或移动覆盖目标前调用它,方便 delta 记录被覆盖的旧内容。

调用图:调用 3 个内部函数(note_existing_path_delta_support, read_file_text, from_abs_path);被 1 处调用(apply_hunks_to_files)。

note_existing_path_delta_support604–617 ↗
async fn note_existing_path_delta_support(
    path: &AbsolutePathBuf,
    fs: &dyn ExecutorFileSystem,
    sandbox: Option<&FileSystemSandboxContext>,
    exact: &mut bool,
)

作用:判断某个现有路径是否适合被精确记录。普通文件可以,目录、符号链接或无法读取元数据的情况就不可靠。

数据流:进去的是路径、文件系统、沙箱和 exact 标记;它读取元数据,如果目标不是普通文件或读取失败,就把 exact 改成 false;出来没有单独结果,只更新标记。

调用关系:apply_hunks_to_files 和 read_optional_file_text_for_delta 会调用它,为 delta 的可信度做前置判断。

调用图:调用 1 个内部函数(from_abs_path);被 2 处调用(apply_hunks_to_files, read_optional_file_text_for_delta);外部调用 1 个(get_metadata)。

write_file_with_missing_parent_retry619–653 ↗
async fn write_file_with_missing_parent_retry(
    fs: &dyn ExecutorFileSystem,
    path_abs: &AbsolutePathBuf,
    contents: Vec<u8>,
    sandbox: Option<&FileSystemSandboxContext>,
) -> anyhow::Resu

作用:写文件时如果父目录不存在,就先创建父目录再重试。这样新增深层路径的文件不会因为目录还没建而直接失败。

数据流:进去的是文件系统、绝对路径、字节内容和沙箱;它先尝试写文件,若报 NotFound,就递归创建父目录,再写一次;出来是成功或带上下文的错误。

调用关系:apply_hunks_to_files 在新增文件和移动到新位置时使用它,把“确保目录存在”的麻烦封装起来。

调用图:调用 2 个内部函数(parent, from_abs_path);外部调用 2 个(create_directory, write_file)。

derive_new_contents_from_chunks662–695 ↗
async fn derive_new_contents_from_chunks(
    path_abs: &AbsolutePathBuf,
    chunks: &[UpdateFileChunk],
    fs: &dyn ExecutorFileSystem,
    sandbox: Option<&FileSystemSandboxContext>,
) -> std::res

作用:根据补丁里的更新片段,算出某个文件改完后的完整内容,但它本身不写回文件。

数据流:进去的是文件路径、更新片段、文件系统和沙箱;它读取原文件,拆成行,调用 compute_replacements 找到要替换的位置,再用 apply_replacements 生成新行,并保证末尾换行;出来是原内容和新内容。

调用关系:apply_hunks_to_files 用它获得真正要写入的内容;unified_diff_from_chunks_with_context 也用它先算新内容再生成预览差异。

调用图:调用 5 个内部函数(apply_replacements, compute_replacements, read_file_text, as_path, from_abs_path);被 2 处调用(apply_hunks_to_files, unified_diff_from_chunks_with_context);外部调用 1 个(new)。

compute_replacements700–788 ↗
fn compute_replacements(
    original_lines: &[String],
    path: &Path,
    chunks: &[UpdateFileChunk],
) -> std::result::Result<Vec<(usize, usize, Vec<String>)>, ApplyPatchError>

作用:把补丁片段翻译成“从第几行开始,删几行,插入哪些行”的替换计划。

数据流:进去的是原文件行、路径和更新片段;它用 seek_sequence 在原文中寻找上下文和旧行,处理纯新增、文件末尾换行等特殊情况;出来是按位置排序的替换列表,找不到就返回 ComputeReplacements 错误。

调用关系:derive_new_contents_from_chunks 调用它。它负责最关键的匹配工作,避免把补丁改到错误位置。

调用图:调用 1 个内部函数(seek_sequence);被 1 处调用(derive_new_contents_from_chunks);外部调用 4 个(new, ComputeReplacements, format!, from_ref)。

apply_replacements792–816 ↗
fn apply_replacements(
    mut lines: Vec<String>,
    replacements: &[(usize, usize, Vec<String>)],
) -> Vec<String>

作用:把替换计划真正套到一组文本行上,得到改完后的行列表。

数据流:进去的是原行列表和替换计划;它从后往前应用每个替换,先删除旧行再插入新行;出来是新的行列表。

调用关系:derive_new_contents_from_chunks 在 compute_replacements 算好计划后调用它。从后往前做,是为了前面的删除插入不影响后面位置。

调用图:被 1 处调用(derive_new_contents_from_chunks)。

unified_diff_from_chunks826–833 ↗
async fn unified_diff_from_chunks(
    path_abs: &AbsolutePathBuf,
    chunks: &[UpdateFileChunk],
    fs: &dyn ExecutorFileSystem,
    sandbox: Option<&FileSystemSandboxContext>,
) -> std::result::Re

作用:为一个更新补丁生成标准的 unified diff 预览。unified diff 是常见的“带上下文的代码差异文本”。

数据流:进去的是路径、更新片段、文件系统和沙箱;它使用默认上下文行数 1,转交给 unified_diff_from_chunks_with_context;出来是包含差异、原内容和新内容的 ApplyPatchFileUpdate。

调用关系:多个 diff 相关测试调用它。它是简化入口,真正工作交给带 context 参数的版本。

调用图:调用 1 个内部函数(unified_diff_from_chunks_with_context);被 5 处调用(test_unified_diff, test_unified_diff_first_line_replacement, test_unified_diff_insert_at_eof, test_unified_diff_interleaved_changes, test_unified_diff_last_line_replacement)。

unified_diff_from_chunks_with_context835–853 ↗
async fn unified_diff_from_chunks_with_context(
    path_abs: &AbsolutePathBuf,
    chunks: &[UpdateFileChunk],
    context: usize,
    fs: &dyn ExecutorFileSystem,
    sandbox: Option<&FileSystemSand

作用:生成可指定上下文行数的 unified diff 预览,让调用方控制差异周围显示多少原文。

数据流:进去的是路径、更新片段、上下文行数、文件系统和沙箱;它先 derive_new_contents_from_chunks 得到原文和新文,再用 TextDiff 生成差异文本;出来是 ApplyPatchFileUpdate。

调用关系:unified_diff_from_chunks 调用它。它复用“算新内容”的逻辑,保证预览和实际应用补丁用的是同一套规则。

调用图:调用 1 个内部函数(derive_new_contents_from_chunks);被 1 处调用(unified_diff_from_chunks);外部调用 1 个(from_lines)。

print_summary857–872 ↗
fn print_summary(
    affected: &AffectedPaths,
    out: &mut impl std::io::Write,
) -> std::io::Result<()>

作用:把成功更新了哪些文件打印成类似 git 的摘要。A 表示新增,M 表示修改,D 表示删除。

数据流:进去的是 AffectedPaths 和输出流;它先写成功标题,再按新增、修改、删除分别写路径;出来是写入成功或 I/O 错误。

调用关系:apply_hunks 在文件操作全部成功后调用它,把内部结果变成用户能看懂的终端输出。

调用图:被 1 处调用(apply_hunks);外部调用 1 个(writeln!)。

tests::wrap_patch885–887 ↗
fn wrap_patch(body: &str) -> String

作用:给测试用的补丁正文自动加上 Begin Patch 和 End Patch 外壳。

数据流:进去的是补丁主体字符串;它拼接标准开头和结尾;出来是一段完整 patch 文本。

调用关系:几乎所有测试都用它,避免每个测试重复写同样的包装格式。

调用图:外部调用 1 个(format!)。

tests::test_add_file_hunk_creates_file_with_contents890–922 ↗
async fn test_add_file_hunk_creates_file_with_contents()

作用:验证新增文件补丁能真的创建文件,并写入正确内容。

数据流:进去的是临时目录中构造的新增文件补丁;它调用 apply_patch,读取 stdout、stderr 和文件内容;出来是断言成功摘要正确、错误输出为空、文件内容正确。

调用关系:它直接测试 apply_patch 的新增路径,依赖 wrap_patch 构造输入。

调用图:调用 2 个内部函数(apply_patch, from_absolute_path);外部调用 7 个(from_utf8, new, wrap_patch, assert_eq!, format!, read_to_string, tempdir)。

tests::test_apply_patch_hunks_accept_relative_and_absolute_paths925–994 ↗
async fn test_apply_patch_hunks_accept_relative_and_absolute_paths()

作用:验证补丁里的路径既可以是相对路径,也可以是绝对路径。

数据流:进去的是同时包含新增、删除、修改的混合补丁;它调用 apply_patch 后检查多个文件的存在和内容;出来是断言所有路径都按预期处理,并且摘要保留用户写的路径样子。

调用关系:它覆盖 apply_patch 到 apply_hunks_to_files 的路径解析行为。

调用图:调用 1 个内部函数(apply_patch);外部调用 7 个(new, wrap_patch, assert!, assert_eq!, format!, write, tempdir)。

tests::test_delete_file_hunk_removes_file997–1023 ↗
async fn test_delete_file_hunk_removes_file()

作用:验证删除文件补丁会把目标文件删掉。

数据流:进去的是一个已存在文件和删除补丁;它执行 apply_patch,再检查输出和文件是否不存在;出来是成功断言。

调用关系:它测试 apply_hunks_to_files 的 DeleteFile 分支,也间接测试 print_summary 的 D 标记。

调用图:调用 2 个内部函数(apply_patch, from_absolute_path);外部调用 8 个(from_utf8, new, wrap_patch, assert!, assert_eq!, format!, write, tempdir)。

tests::test_update_file_hunk_modifies_content1026–1061 ↗
async fn test_update_file_hunk_modifies_content()

作用:验证普通修改补丁能把文件中的旧行替换成新行。

数据流:进去的是包含 foo、bar 的文件和把 bar 改成 baz 的补丁;它调用 apply_patch 后读取文件;出来是文件内容变成 foo、baz,并且摘要显示 M。

调用关系:它测试 derive_new_contents_from_chunks、compute_replacements 和 apply_replacements 组合起来是否能完成基础替换。

调用图:调用 2 个内部函数(apply_patch, from_absolute_path);外部调用 8 个(from_utf8, new, wrap_patch, assert_eq!, format!, read_to_string, write, tempdir)。

tests::test_update_file_hunk_can_move_file1064–1102 ↗
async fn test_update_file_hunk_can_move_file()

作用:验证更新补丁可以同时修改内容并把文件移动到新路径。

数据流:进去的是源文件、目标路径和带 Move to 的补丁;它执行 apply_patch;出来是源文件消失、目标文件出现且内容更新,摘要显示修改目标。

调用关系:它覆盖 apply_hunks_to_files 的移动分支,包括先写目标再删除源文件。

调用图:调用 2 个内部函数(apply_patch, from_absolute_path);外部调用 9 个(from_utf8, new, wrap_patch, assert!, assert_eq!, format!, read_to_string, write, tempdir)。

tests::test_failed_move_returns_committed_destination_delta1106–1157 ↗
async fn test_failed_move_returns_committed_destination_delta()

作用:验证移动过程中如果目标已写好但删除源文件失败,失败结果会记录已经写入目标这件事。

数据流:进去的是一个因权限导致源文件无法删除的移动补丁;它调用 apply_patch 并期望失败;出来是断言 delta 记录了目标新增,源文件仍在,目标文件也已写入。

调用关系:它专门保护 ApplyPatchFailure 和 AppliedPatchDelta 的语义,确保中途失败不会丢失已提交改动的信息。

调用图:调用 2 个内部函数(apply_patch, from_absolute_path);外部调用 9 个(new, wrap_patch, assert!, assert_eq!, from_mode, create_dir, set_permissions, write, tempdir)。

tests::test_multiple_update_chunks_apply_to_single_file1162–1204 ↗
async fn test_multiple_update_chunks_apply_to_single_file()

作用:验证一个文件里多个分散的更新片段都能应用,而且摘要里只列一次这个文件。

数据流:进去的是含四行的文件和两个修改片段;它执行 apply_patch 后读文件和输出;出来是两个位置都被替换,stdout 只显示一个 M。

调用关系:它测试 compute_replacements 连续查找多个 chunk 的能力。

调用图:调用 2 个内部函数(apply_patch, from_absolute_path);外部调用 8 个(from_utf8, new, wrap_patch, assert_eq!, format!, read_to_string, write, tempdir)。

tests::test_update_file_hunk_interleaved_changes1211–1265 ↗
async fn test_update_file_hunk_interleaved_changes()

作用:验证替换、删除式替换和文件末尾追加混在一起时也能正确执行。

数据流:进去的是六行文件和三个非相邻修改片段;它执行 apply_patch;出来是所有目标行按预期变化,并追加新行 g。

调用关系:它覆盖 compute_replacements 对复杂顺序和 End of File 标记的处理。

调用图:调用 2 个内部函数(apply_patch, from_absolute_path);外部调用 8 个(from_utf8, new, wrap_patch, assert_eq!, format!, read_to_string, write, tempdir)。

tests::test_pure_addition_chunk_followed_by_removal1268–1301 ↗
async fn test_pure_addition_chunk_followed_by_removal()

作用:验证先有纯新增片段、后有删除替换片段时不会因为行号变化而出错。

数据流:进去的是三行文件和两个更新片段;它执行 apply_patch 后读取内容;出来是替换后的行和新增行都在正确位置。

调用关系:它重点测试 apply_replacements 从后往前应用替换的设计是否有效。

调用图:调用 2 个内部函数(apply_patch, from_absolute_path);外部调用 7 个(new, wrap_patch, assert_eq!, format!, read_to_string, write, tempdir)。

tests::test_update_line_with_unicode_dash1310–1355 ↗
async fn test_update_line_with_unicode_dash()

作用:验证补丁里用普通 ASCII 短横线,也能匹配文件里常见的 Unicode 破折号或不换行连字符。

数据流:进去的是含特殊标点的文件,以及用普通短横线写的替换补丁;它执行 apply_patch;出来是文件成功替换,没有错误输出。

调用关系:它保护底层 seek_sequence 的模糊匹配能力,确保现实代码里的标点差异不会让补丁失败。

调用图:调用 2 个内部函数(apply_patch, from_absolute_path);外部调用 8 个(from_utf8, new, wrap_patch, assert_eq!, format!, read_to_string, write, tempdir)。

tests::test_unified_diff1358–1404 ↗
async fn test_unified_diff()

作用:验证根据补丁片段生成的 unified diff 是否符合预期。

数据流:进去的是一个包含两处修改的文件和补丁;它 parse_patch 后取出 chunks,调用 unified_diff_from_chunks;出来是断言差异文本、原内容、新内容都正确。

调用关系:它测试 unified_diff_from_chunks 与 derive_new_contents_from_chunks 的配合。

调用图:调用 2 个内部函数(parse_patch, unified_diff_from_chunks);外部调用 6 个(wrap_patch, assert_eq!, format!, write, panic!, tempdir)。

tests::test_unified_diff_first_line_replacement1407–1445 ↗
async fn test_unified_diff_first_line_replacement()

作用:验证替换文件第一行时,生成的 diff 范围和内容正确。

数据流:进去的是三行文件和替换第一行的补丁;它解析补丁并生成 diff;出来是断言 diff 从第一行开始,原内容和新内容正确。

调用关系:它覆盖 unified_diff_from_chunks 在文件开头边界的行为。

调用图:调用 2 个内部函数(parse_patch, unified_diff_from_chunks);外部调用 6 个(wrap_patch, assert_eq!, format!, write, panic!, tempdir)。

tests::test_unified_diff_last_line_replacement1448–1487 ↗
async fn test_unified_diff_last_line_replacement()

作用:验证替换文件最后一行时,生成的 diff 不会因为末尾换行处理出错。

数据流:进去的是三行文件和替换最后一行的补丁;它生成 diff;出来是断言 diff 只显示相关尾部上下文和替换内容。

调用关系:它保护 derive_new_contents_from_chunks 和 TextDiff 在文件结尾边界上的组合效果。

调用图:调用 2 个内部函数(parse_patch, unified_diff_from_chunks);外部调用 6 个(wrap_patch, assert_eq!, format!, write, panic!, tempdir)。

tests::test_unified_diff_insert_at_eof1490–1526 ↗
async fn test_unified_diff_insert_at_eof()

作用:验证在文件末尾插入新行时,生成的 diff 正确显示追加内容。

数据流:进去的是三行文件和 End of File 追加补丁;它解析后调用 unified_diff_from_chunks;出来是断言 diff 显示 baz 后新增 quux。

调用关系:它覆盖 compute_replacements 对纯新增和文件末尾标记的处理,并检查预览结果。

调用图:调用 2 个内部函数(parse_patch, unified_diff_from_chunks);外部调用 6 个(wrap_patch, assert_eq!, format!, write, panic!, tempdir)。

tests::test_unified_diff_interleaved_changes1529–1612 ↗
async fn test_unified_diff_interleaved_changes()

作用:验证复杂的多处修改既能生成正确 diff,也能实际应用到文件。

数据流:进去的是六行文件和三个修改片段;它先生成 diff 并断言,再调用 apply_patch 真正写文件;出来是差异文本和最终文件内容都符合预期。

调用关系:它把 unified_diff_from_chunks 和 apply_patch 放在同一个场景里比较,确保预览和实际执行一致。

调用图:调用 4 个内部函数(apply_patch, parse_patch, unified_diff_from_chunks, from_absolute_path);外部调用 8 个(new, wrap_patch, assert_eq!, format!, read_to_string, write, panic!, tempdir)。

tests::test_apply_patch_fails_on_write_error1616–1642 ↗
async fn test_apply_patch_fails_on_write_error()

作用:验证写文件失败时,返回的 delta 会标记为不精确。

数据流:进去的是一个没有写权限的目录和新增文件补丁;它调用 apply_patch 并期望失败;出来是断言 failure.delta().is_exact() 为假。

调用关系:它保护 apply_hunks_to_files 里写失败会把 delta.exact 置为 false 的逻辑。

调用图:调用 2 个内部函数(apply_patch, from_absolute_path);外部调用 7 个(new, wrap_patch, assert!, from_mode, create_dir, set_permissions, tempdir)。

tests::test_unreadable_destinations_return_inexact_delta1645–1671 ↗
async fn test_unreadable_destinations_return_inexact_delta()

作用:验证目标文件内容无法作为文本读取时,delta 会被标记为不精确。

数据流:进去的是一个二进制目标文件,以及新增覆盖或移动覆盖补丁;它执行 apply_patch;出来是断言返回的 delta 不是精确的。

调用关系:它测试 read_optional_file_text_for_delta 和 note_existing_path_delta_support 在不可读内容上的保守处理。

调用图:调用 2 个内部函数(apply_patch, from_absolute_path);外部调用 5 个(new, wrap_patch, assert!, write, tempdir)。

git-utils/src/apply.rs源码 ↗
domain_logicrequest handling

这个文件像一个“补丁代办员”。调用者给它一个目录和一段 unified diff(统一格式的补丁文本,描述哪些文件哪几行要改),它先确认这个目录属于哪个 Git 仓库,再把补丁写进临时文件,然后运行系统命令 git apply。它支持正常应用、反向撤销补丁,也支持 preflight,也就是只检查能不能应用、不真的改工作区。应用完后,它不会只把一大段命令行输出丢给上层,而是会分析 stdout/stderr(命令的标准输出和错误输出),分出哪些文件应用成功、哪些被跳过、哪些发生冲突。文件里还有一套路径提取和转义处理,专门应对文件名带空格、引号、制表符等麻烦情况。测试部分会临时建 Git 仓库,验证新增、冲突、缺文件、撤销和预检查这些关键场景。

函数细节28
apply_git_patch41–124 ↗
fn apply_git_patch(req: &ApplyGitRequest) -> io::Result<ApplyGitResult>

作用:这是本文件的主入口:把调用者给的补丁交给 git apply 执行。它还负责区分真实应用和预检查,并把命令结果整理成结构化报告。

数据流:输入是一份 ApplyGitRequest,里面有工作目录、补丁文本、是否反向撤销、是否只预检查。它先找 Git 根目录,把补丁写成临时文件,必要时先暂存相关文件,然后拼出 git apply 参数并执行;最后解析 git 输出,返回退出码、成功文件、跳过文件、冲突文件、原始输出和可记录到日志里的命令字符串。

调用关系:上层测试和实际调用者都会从这里开始。它把找仓库的事交给 resolve_git_root,把写临时补丁交给 write_temp_patch,把运行 git 交给 run_git,把日志命令交给 render_command_for_log,把结果分类交给 parse_git_apply_output;反向应用时还会让 stage_paths 先做一次尽力暂存。

调用图:调用 6 个内部函数(parse_git_apply_output, render_command_for_log, resolve_git_root, run_git, stage_paths, write_temp_patch);被 6 处调用(apply_add_success, apply_modify_conflict, apply_modify_skipped_missing_index, apply_then_revert_success, preflight_blocks_partial_changes, revert_preflight_does_not_stage_index);外部调用 3 个(new, var, vec!)。

resolve_git_root126–142 ↗
fn resolve_git_root(cwd: &Path) -> io::Result<PathBuf>

作用:这个函数用 git rev-parse 找出当前目录所属 Git 仓库的根目录。这样后续命令都能在正确的仓库顶层运行,避免路径对不上。

数据流:输入是一个目录路径。它在这个目录里运行 git rev-parse --show-toplevel;如果成功,就把输出的根目录转成 PathBuf 返回;如果失败,就返回一个“这不是 Git 仓库”的错误。

调用关系:apply_git_patch 一开始就调用它。只有先确定仓库根目录,后面的临时补丁应用、路径暂存和日志显示才有可靠的参照点。

调用图:被 1 处调用(apply_git_patch);外部调用 5 个(from, from_utf8_lossy, new, other, format!)。

write_temp_patch144–149 ↗
fn write_temp_patch(diff: &str) -> io::Result<(tempfile::TempDir, PathBuf)>

作用:这个函数把补丁文本写进一个临时文件。git apply 需要读文件形式的补丁,所以这里负责把字符串变成磁盘上的 patch.diff。

数据流:输入是一段补丁字符串。它创建一个临时目录,在里面写入 patch.diff,然后返回临时目录对象和补丁文件路径;临时目录对象活着时,文件就不会被自动删掉。

调用关系:apply_git_patch 在运行 git 前调用它。它提供给 git apply 一个实际可读的补丁文件路径。

调用图:被 1 处调用(apply_git_patch);外部调用 2 个(write, tempdir)。

run_git151–164 ↗
fn run_git(cwd: &Path, git_cfg: &[String], args: &[String]) -> io::Result<(i32, String, String)>

作用:这个函数统一负责真正启动 git 命令。它把参数传给系统里的 git,并收集退出码、正常输出和错误输出。

数据流:输入是运行目录、额外的 git 配置参数和普通命令参数。它组装并执行 git 命令,把字节形式的输出转成字符串,最后返回退出码、stdout 和 stderr。

调用关系:apply_git_patch 用它执行预检查的 git apply --check,也用它执行真正的 git apply。后续 parse_git_apply_output 会继续读取它返回的输出。

调用图:被 1 处调用(apply_git_patch);外部调用 2 个(from_utf8_lossy, new)。

quote_shell166–175 ↗
fn quote_shell(s: &str) -> String

作用:这个函数把一段命令参数处理成适合写进日志的 shell 字符串。它不是为了执行命令,而是为了让日志里的命令看起来安全、可复制。

数据流:输入是一段字符串。如果里面只有简单字符,就原样返回;如果有空格、引号等特殊字符,就用单引号包起来,并正确转义里面的单引号。

调用关系:render_command_for_log 会反复调用它,给目录、git 配置和每个参数做日志展示用的引用处理。

调用图:被 1 处调用(render_command_for_log);外部调用 1 个(format!)。

render_command_for_log177–191 ↗
fn render_command_for_log(cwd: &Path, git_cfg: &[String], args: &[String]) -> String

作用:这个函数生成一条人能看懂、能贴到终端里参考的 git 命令日志。它说明命令在哪个目录下运行、带了哪些参数。

数据流:输入是工作目录、git 配置参数和 git 命令参数。它把这些片段逐个用 quote_shell 包装,再拼成类似“cd 某目录 && git ...”的字符串返回。

调用关系:apply_git_patch 在预检查和真实应用前都会调用它,把即将执行的命令保存到结果里,方便排错或记录。

调用图:调用 1 个内部函数(quote_shell);被 1 处调用(apply_git_patch);外部调用 2 个(new, format!)。

extract_paths_from_patch194–212 ↗
fn extract_paths_from_patch(diff_text: &str) -> Vec<String>

作用:这个函数从补丁文本里找出涉及到的文件路径。它主要看 diff --git 这一类补丁头,适合在真正应用前知道哪些文件可能受影响。

数据流:输入是一整段补丁文本。它逐行寻找 diff --git 开头的行,拆出 a/ 和 b/ 两边的路径,去掉前缀、过滤 /dev/null,再用有序集合去重,最后返回路径列表。

调用关系:stage_paths 会调用它来决定哪些文件需要先暂存;多个测试也直接调用它,确认带引号、/dev/null 和转义字符的路径能被正确识别。

调用图:调用 2 个内部函数(normalize_diff_path, parse_diff_git_paths);被 4 处调用(stage_paths, extract_paths_handles_quoted_headers, extract_paths_ignores_dev_null_header, extract_paths_unescapes_c_style_in_quoted_headers);外部调用 1 个(new)。

parse_diff_git_paths214–219 ↗
fn parse_diff_git_paths(line: &str) -> Option<(String, String)>

作用:这个函数专门拆解 diff --git 后面的两个路径。它能处理普通路径,也能处理被引号包起来的路径。

数据流:输入是 diff --git 后面的剩余文本。它连续调用 read_diff_git_token 读出第一个和第二个路径;如果两个都读到了,就返回这一对路径,否则返回空。

调用关系:extract_paths_from_patch 找到补丁头以后,会把实际拆路径的细活交给它。它再把“读一个路径片段”的工作交给 read_diff_git_token。

调用图:调用 1 个内部函数(read_diff_git_token);被 1 处调用(extract_paths_from_patch)。

read_diff_git_token221–255 ↗
fn read_diff_git_token(chars: &mut std::iter::Peekable<std::str::Chars<'_>>) -> Option<String>

作用:这个函数从一串字符里读取一个路径片段。它照顾了文件名带空格时被引号包住、以及里面有反斜杠转义的情况。

数据流:输入是一个可逐字符读取的位置。它先跳过空白,再判断有没有引号;有引号就读到配对引号为止,没有引号就读到空白为止;读到的带转义字符串会交给 unescape_c_string 还原,最后返回路径文本。

调用关系:parse_diff_git_paths 用它读出 diff --git 行里的两个路径。它在遇到引号路径时会调用 unescape_c_string,把 Git 风格的转义还原成人类看到的文件名。

调用图:调用 1 个内部函数(unescape_c_string);被 1 处调用(parse_diff_git_paths);外部调用 4 个(next, peek, new, matches!)。

normalize_diff_path257–270 ↗
fn normalize_diff_path(raw: &str, prefix: &str) -> Option<String>

作用:这个函数把补丁里的路径整理成项目内的真实相对路径。它会去掉 a/ 或 b/ 这样的补丁前缀,并忽略表示“没有文件”的 /dev/null。

数据流:输入是原始路径和要去掉的前缀。它先去空白,空路径直接丢弃;如果路径是 /dev/null 或类似 a/dev/null,就返回空;否则去掉前缀并返回干净路径。

调用关系:extract_paths_from_patch 在拆出 a、b 两边路径后调用它,保证最后给 stage_paths 或调用者看的路径是仓库里的正常文件名。

调用图:被 1 处调用(extract_paths_from_patch);外部调用 1 个(format!)。

unescape_c_string272–317 ↗
fn unescape_c_string(input: &str) -> String

作用:这个函数把类似 C 语言字符串里的转义写法还原成真实字符。比如 \t 会变成制表符,\n 会变成换行,引号转义也会恢复。

数据流:输入是一段可能带反斜杠转义的字符串。它逐字符扫描,普通字符直接复制;遇到反斜杠就按后面的字符决定要变成换行、制表符、引号、八进制字符等,最后输出还原后的字符串。

调用关系:read_diff_git_token 用它处理补丁头里的带引号路径;parse_git_apply_output 里的内部 add 逻辑也用同样思路处理 git 输出中带引号的文件名。

调用图:被 1 处调用(read_diff_git_token);外部调用 2 个(with_capacity, from_u32)。

stage_paths320–342 ↗
fn stage_paths(git_root: &Path, diff: &str) -> io::Result<()>

作用:这个函数在反向应用补丁前,尽力把补丁涉及且当前真实存在的文件加入 Git 暂存区。这样可以减少 git apply -R 因索引状态不匹配而失败的机会。

数据流:输入是 Git 根目录和补丁文本。它先从补丁里提取路径,只保留磁盘上确实存在的文件,然后运行 git add -- 这些文件;即使命令失败,它也不把失败当成硬错误,而是返回成功。

调用关系:apply_git_patch 只在“反向撤销且不是预检查”时调用它。它依赖 extract_paths_from_patch 找文件清单,然后自己运行 git add 做最佳努力的准备工作。

调用图:调用 1 个内部函数(extract_paths_from_patch);被 1 处调用(apply_git_patch);外部调用 5 个(new, join, new, new, symlink_metadata)。

parse_git_apply_output347–589 ↗
fn parse_git_apply_output(
    stdout: &str,
    stderr: &str,
) -> (Vec<String>, Vec<String>, Vec<String>)

作用:这个函数把 git apply 的文字输出翻译成三类文件:应用成功、被跳过、发生冲突。它解决的是“命令行说了一堆话,程序不知道重点在哪”的问题。

数据流:输入是 stdout 和 stderr 两段文本。它合并后逐行检查,用一批不区分大小写的正则表达式(按模式找文字的工具)识别“cleanly”“with conflicts”“patch failed”等信息;再把路径放进 applied、skipped、conflicted 三个集合,并按“冲突优先于成功,成功优先于跳过”的规则整理后返回三个列表。

调用关系:apply_git_patch 在 git 命令结束后调用它生成结构化结果;测试 parse_output_unescapes_quoted_paths 也直接调用它,确认带转义的路径能被正确解析。它内部依靠 regex_ci 创建匹配规则。

调用图:被 2 处调用(apply_git_patch, parse_output_unescapes_quoted_paths);外部调用 2 个(new, new)。

regex_ci591–593 ↗
fn regex_ci(pat: &str) -> Regex

作用:这个小工具用来创建“不区分大小写”的正则表达式。这样 git 输出里大小写略有不同,也能被同一套规则识别。

数据流:输入是一段正则模式文本。它在前面加上不区分大小写的标记,再创建 Regex;如果模式写错,就直接 panic,也就是让程序在开发时暴露错误。

调用关系:parse_git_apply_output 里的那些静态匹配规则会用它来创建正则。它本身不参与业务判断,只是统一生成匹配工具。

调用图:外部调用 2 个(new, format!)。

tests::env_lock602–605 ↗
fn env_lock() -> &'static Mutex<()>

作用:这个测试辅助函数提供一把全局互斥锁(一把锁,防止多个测试同时改同类环境)。它让涉及 Git 和环境变量的测试不会互相打架。

数据流:它不需要外部输入。第一次调用时创建一个静态 Mutex,之后每次都返回同一把锁的引用;测试拿到锁后就能串行执行敏感步骤。

调用关系:多个集成式测试在开始时调用它并加锁。它不参与正式功能,只保证测试运行更稳定。

调用图:外部调用 1 个(new)。

tests::run607–618 ↗
fn run(cwd: &Path, args: &[&str]) -> (i32, String, String)

作用:这个测试辅助函数在指定目录运行一条命令,并拿回退出码和输出。测试用它来执行 git init、git add、git commit 等命令。

数据流:输入是工作目录和命令参数数组。它启动数组第一个元素代表的程序,把后续元素作为参数,收集 stdout、stderr 和退出码后返回。

调用关系:tests::init_repo 和多个测试用它准备临时仓库、提交文件、查看暂存区状态。它相当于测试里的简化版命令执行器。

调用图:外部调用 2 个(from_utf8_lossy, new)。

tests::init_repo620–628 ↗
fn init_repo() -> tempfile::TempDir

作用:这个测试辅助函数创建一个全新的临时 Git 仓库。这样每个测试都有干净的小沙盒,不会影响真实项目目录。

数据流:它创建临时目录,在里面运行 git init,并设置最基本的用户名和邮箱,最后返回这个临时目录对象。

调用关系:几乎所有会真实调用 apply_git_patch 的测试都先调用它。它通过 tests::run 执行 Git 初始化命令。

调用图:外部调用 2 个(run, tempdir)。

tests::read_file_normalized630–634 ↗
fn read_file_normalized(path: &Path) -> String

作用:这个测试辅助函数读取文件内容,并把 Windows 风格换行统一成普通换行。这样测试在不同操作系统上比较文本时不容易误判。

数据流:输入是文件路径。它读取整个文件为字符串,把 \r\n 替换成 \n,然后返回规范化后的文本。

调用关系:撤销相关测试用它检查文件内容是否真的从修改后恢复到原样。它只服务测试断言。

调用图:外部调用 1 个(read_to_string)。

tests::extract_paths_handles_quoted_headers637–641 ↗
fn extract_paths_handles_quoted_headers()

作用:这个测试确认补丁头里的带空格文件名即使用引号包着,也能被正确提取出来。

数据流:它构造一段包含 "hello world.txt" 的补丁,调用 extract_paths_from_patch,然后断言结果正好是 hello world.txt。

调用关系:它直接验证 extract_paths_from_patch,并间接覆盖 parse_diff_git_paths 和 read_diff_git_token 对引号路径的处理。

调用图:调用 1 个内部函数(extract_paths_from_patch);外部调用 1 个(assert_eq!)。

tests::extract_paths_ignores_dev_null_header644–648 ↗
fn extract_paths_ignores_dev_null_header()

作用:这个测试确认 /dev/null 这种“文件不存在”的补丁占位符不会被当成真实文件路径。

数据流:它构造一个新增文件补丁,其中旧文件一边是 /dev/null,调用 extract_paths_from_patch 后,断言只得到 ok.txt。

调用关系:它验证 extract_paths_from_patch 和 normalize_diff_path 的配合,确保新增文件场景不会把 dev/null 误加入路径列表。

调用图:调用 1 个内部函数(extract_paths_from_patch);外部调用 1 个(assert_eq!)。

tests::extract_paths_unescapes_c_style_in_quoted_headers651–655 ↗
fn extract_paths_unescapes_c_style_in_quoted_headers()

作用:这个测试确认补丁头里带 C 风格转义的文件名能被还原。比如文件名里的 \t 应该变成真正的制表符。

数据流:它构造一段路径里含 hello\tworld.txt 的补丁,调用 extract_paths_from_patch,然后断言返回的路径包含真实制表符。

调用关系:它直接检验 read_diff_git_token 调用 unescape_c_string 的效果,保证特殊文件名不会被错误识别。

调用图:调用 1 个内部函数(extract_paths_from_patch);外部调用 1 个(assert_eq!)。

tests::parse_output_unescapes_quoted_paths658–664 ↗
fn parse_output_unescapes_quoted_paths()

作用:这个测试确认 git apply 输出里带引号和转义的路径,也能被解析成真实文件名。

数据流:它准备一段类似 error: patch failed: "hello\tworld.txt":1 的 stderr,调用 parse_git_apply_output,然后断言 skipped 列表里是带真实制表符的路径。

调用关系:它直接验证 parse_git_apply_output 对失败路径的提取和反转义能力。

调用图:调用 1 个内部函数(parse_git_apply_output);外部调用 1 个(assert_eq!)。

tests::apply_add_success667–683 ↗
fn apply_add_success()

作用:这个测试验证最基本的成功场景:用补丁新增一个文件。它证明主入口真的能把补丁交给 Git 并改变工作区。

数据流:它先加锁并创建临时仓库,再构造一个新增 hello.txt 的补丁,调用 apply_git_patch,最后检查退出码为 0 且文件确实存在。

调用关系:它从测试侧调用 apply_git_patch,覆盖 resolve_git_root、write_temp_patch、run_git 和结果返回的主流程。

调用图:调用 1 个内部函数(apply_git_patch);外部调用 4 个(assert!, assert_eq!, env_lock, init_repo)。

tests::apply_modify_conflict686–706 ↗
fn apply_modify_conflict()

作用:这个测试验证修改同一行时发生冲突的场景。它确保补丁无法干净应用时,函数不会假装成功。

数据流:它创建仓库并提交原始文件,然后本地先改同一行,再用补丁尝试改成另一个内容;调用 apply_git_patch 后,断言退出码不是 0。

调用关系:它调用 tests::run 准备提交状态,再调用 apply_git_patch 检查冲突路径。主要验证真实 git apply --3way 失败时结果能传回来。

调用图:调用 1 个内部函数(apply_git_patch);外部调用 5 个(assert_ne!, env_lock, init_repo, run, write)。

tests::apply_modify_skipped_missing_index709–723 ↗
fn apply_modify_skipped_missing_index()

作用:这个测试验证修改一个 Git 索引里不存在的文件会失败。它防止程序把“找不到原文件”的补丁误认为已应用。

数据流:它创建空仓库,构造一个试图修改 ghost.txt 的补丁,然后调用 apply_git_patch,并断言退出码不是 0。

调用关系:它直接调用 apply_git_patch,重点覆盖 git apply 对缺失文件的错误返回,以及 parse_git_apply_output 识别跳过路径的能力。

调用图:调用 1 个内部函数(apply_git_patch);外部调用 3 个(assert_ne!, env_lock, init_repo)。

tests::apply_then_revert_success726–759 ↗
fn apply_then_revert_success()

作用:这个测试验证先应用补丁、再反向撤销补丁可以成功。它证明 revert 模式能把文件恢复回原内容。

数据流:它创建仓库并提交 orig 内容,先用 apply_git_patch 把 orig 改成 ORIG,再用 revert=true 的请求应用同一补丁的反方向,最后读取文件确认又回到 orig。

调用关系:它调用 apply_git_patch 两次。第二次会触发 stage_paths,让反向应用前先尽力暂存相关文件,验证这条特殊流程是有效的。

调用图:调用 1 个内部函数(apply_git_patch);外部调用 6 个(assert_eq!, env_lock, init_repo, read_file_normalized, run, write)。

tests::revert_preflight_does_not_stage_index762–804 ↗
fn revert_preflight_does_not_stage_index()

作用:这个测试确认反向撤销的预检查不会偷偷改暂存区。预检查的承诺是“只看能不能做,不动现场”。

数据流:它创建仓库、应用并提交一个修改,然后记录预检查前的暂存区内容;接着用 revert=true 且 preflight=true 调用 apply_git_patch,最后确认暂存区没变、文件内容也没变。

调用关系:它验证 apply_git_patch 在 preflight 模式下不会调用 stage_paths,而是走 git apply --check 的只检查路径。

调用图:调用 1 个内部函数(apply_git_patch);外部调用 6 个(assert_eq!, env_lock, init_repo, read_file_normalized, run, write)。

tests::preflight_blocks_partial_changes807–846 ↗
fn preflight_blocks_partial_changes()

作用:这个测试确认预检查失败时不会产生半截改动。即使补丁里有一部分本来能成功,也不能先改了再报错。

数据流:它构造一个多文件补丁:一个新增文件可行,另一个修改不存在文件会失败。先用 preflight=true 调用,确认退出失败且可新增文件没有出现;再用非预检查调用,确认日志命令里不带 --check。

调用关系:它通过 apply_git_patch 验证预检查分支使用 git apply --check,并验证普通应用分支不会误带 --check。

调用图:调用 1 个内部函数(apply_git_patch);外部调用 4 个(assert!, assert_ne!, env_lock, init_repo)。