Sourcegraph Code Search:跨代码宿主索引、SCIP 与权限治理
当代码不在一个地方,搜索结果先要回答“看见了什么”
一次支付接口升级同时牵动 GitHub.com 上的 SDK、内网 GitLab 上的服务、Bitbucket Data Center 中的部署模板。负责人在三个网页里分别搜索旧接口名,合并结果后宣布“只影响 18 个仓库”。上线前,另一个同事却在一条长期维护分支发现了第 19 处调用。问题不在正则,而在搜索面:哪些代码宿主已经连接,哪些仓库完成克隆,哪些修订进入索引,哪些结果又被当前用户的权限过滤,都没有证据。
Sourcegraph 的价值不是多一个搜索框,而是把多个代码宿主里的仓库镜像、全文索引、查询执行和代码宿主权限收进同一条可观察链路。先把这条链路跑通,再谈跨仓影响分析;否则“零命中”只能表示当前身份在当前语料上没有拿到结果。
先把搜索链路拆成六个对象
用户输入查询后,Sourcegraph 并不是实时调用 GitHub、GitLab 或 Bitbucket 的搜索 API。代码宿主连接先同步仓库元数据,gitserver 再克隆并维护 Git 数据;默认分支等被选中的修订由 Zoekt 建立全文索引。前端接收查询,搜索服务将它拆到索引分片或 Git 数据路径,最后再按 repository authorization 过滤当前用户可见仓库。SCIP 索引走另一条上传和存储链路,为定义、引用和实现关系提供编译器级语义。
这六类状态不能互相替代:仓库出现在管理页,只证明元数据同步;镜像完成,不证明默认分支索引新鲜;全文搜索命中,不证明 SCIP 已上传;管理员能看到仓库,也不证明普通用户权限正确。排障时沿对象逐层取证,比反复改查询更快。
选择托管还是自托管,再开始安装
Sourcegraph Cloud 是单租户托管实例,平台负责升级、监控和备份,但实例必须能连接代码宿主。官方列出的云区域、私网连接方式、日志留存和可选能力会变化,采购时应在 Sourcegraph Cloud 重新确认区域、网络、审计导出和合同条件。源码、仓库元数据、代码宿主凭证和用户元数据都会进入托管边界,因此“网页只展示搜索结果”不等于“源码不驻留”。
自托管把 Git 镜像、全文索引、SCIP 数据库、应用数据库和日志放进组织控制的基础设施,但不等于开源免费版。当前官方将 self-hosted 标为 Enterprise 支持面,Docker Compose 文档还明确要求超过 10 个用户的实例取得 Sourcegraph license;采购必须分别确认订阅、席位、功能和支持条款,不能从“镜像可以拉取”推导出生产使用许可。官方 部署选择 将 Helm 作为健壮、可扩展的多节点方案,将 Docker Compose 作为单节点入口;Kustomize 已进入计划弃用,Machine Images 的 sunset 边界标为 Sourcegraph 7.0.0,ARM/ARM64 也不属于受支持的生产部署。部署方法之间迁移涉及完整重部署和数据库迁移,试点不要用待退出形态或临时单容器形态假装生产拓扑。
版本基线要同时固定服务端发布标签与 src CLI。官方 Docker Compose 安装页当前以 v7.5.4453 展示 7.x 标签形式,但这个示例不是组织自动追新的理由;升级文档要求按受支持路径处理数据库 schema 和 out-of-band migration,跨多个 minor 的 multiversion upgrade 还会引入停机。src 最新版若与实例不兼容,应回到与实例发布线兼容的 CLI,而不是只升级客户端。
一个可审计的单节点试点可以从官方 Compose 部署仓库开始:
git clone https://github.com/sourcegraph/deploy-sourcegraph-docker.git
cd deploy-sourcegraph-docker
git checkout <经过组织批准的-v7.x.y-发布标签> -b release
cd docker-compose
docker compose up -d
docker ps --filter="name=sourcegraph-frontend-0"预期证据是 sourcegraph-frontend-0 进入 healthy,浏览器能打开实例,Site admin 的服务状态没有持续失败。<经过组织批准的发布标签> 必须固定为团队验证过的发布标签;追随浮动分支会让升级、回滚和数据迁移失去共同基线。生产配置使用 docker-compose.override.yaml 保存资源、外部数据库、证书和环境变量覆盖,不直接改官方基础文件,具体结构见 Docker Compose 部署。
首次登录后立即接企业身份源并关闭不需要的内建注册。Sourcegraph 的登录认证与仓库授权是两件事:OIDC、SAML 或 OAuth 解决“你是谁”,repository permissions 解决“你能看哪些仓库”。不要因为 SSO 成功就把搜索开放给全员。
连接多个代码宿主时,配置字段就是搜索边界
站点管理员在 Site admin → Code hosts 添加连接。不同宿主的 JSON 模式不同,不能把一份 PAT 粘给所有连接;优先使用宿主支持的 App 或服务身份,并把连接拆到可独立轮换、可独立停用的组织边界。官方 代码宿主连接 会列出各宿主的同步、认证和权限能力。
以 GitHub 连接为例,管理员通常要决定这些字段:
url 决定 API 与克隆所指向的 GitHub.com 或 GHES 实例;写错主机可能连接到同名但不同信任域的服务。token 或 GitHub App 凭证决定 Sourcegraph 能枚举和克隆哪些仓库;它是平台采集身份,不是最终用户的可见权限。repositoryQuery 或连接支持的仓库选择字段决定纳入哪些组织、用户或仓库。选择过宽增加镜像、索引、API 限流和数据驻留成本;选择过窄会产生静默漏检。
exclude 在同步入口排除仓库,适合隔离依法不得复制的仓库、超大生成物仓库或已退役项目。排除后应等待仓库状态页消失,并按组织数据删除流程确认副本与备份生命周期。authorization 相关配置决定是否从代码宿主同步用户与仓库关系。未建立 repository authorization 之前,不应导入私有仓库并开放普通用户访问。
配置后先点 Test connection,再到 Site admin → Repositories 观察三个阶段:仓库被发现、克隆完成、索引完成。若仓库已出现但不更新,检查 worker 的宿主 API 日志与 gitserver 克隆日志;若连接测试出现 DNS、TLS 或超时错误,先修通实例到代码宿主的网络和 CA 信任,不要用全局跳过 TLS 验证作为长期解法。官方 添加仓库与排障 给出了这些证据入口。
项目接入不应靠管理员手工记忆。平台团队维护一份连接清单,至少记录代码宿主、组织选择表达式、排除理由、采集身份 owner、授权机制、数据分类、预计仓库数与退役日期。新项目只要进入受管组织并符合选择表达式就自动被发现;敏感项目则通过明确排除和审批进入独立实例或不采集路径。
第一个正向实验:跨宿主命中必须能解释
准备两个测试仓库,分别放在不同代码宿主。两个仓库都提交同一无敏感信息的标记:
SOURCEGRAPH_CROSS_HOST_PROBE_7F3A等待仓库状态页显示克隆与索引完成,然后用 CLI 做可保存的查询。src 是单文件客户端,安装和登录方式见 src quickstart:
export SRC_ENDPOINT=https://sourcegraph.example.com
src login
src search 'SOURCEGRAPH_CROSS_HOST_PROBE_7F3A count:all'预期输出包含两个不同宿主前缀的仓库路径,并指向标记所在文件。count:all 要求搜索等待更多结果,适合校验候选集;它仍受实例查询上限、超时、当前用户权限、索引跳过规则和仓库选择面影响,不能升级成数学意义上的全库证明。持续时间很长或结果量巨大时,应使用官方 Search Jobs,并检查任务日志中每个 repository-revision 子任务是否成功。
接着把查询收敛成项目日常使用的 search context。Search context 保存仓库与修订集合,团队可以用它表示“支付域”“移动端 SDK”或“一组受支持发布线”,而不是每次复制长 repo: 正则。context: 改变默认目标集合;它不会绕过仓库权限,也不会把尚未同步的仓库变出来。配置静态仓库清单时用 HEAD 表示默认分支,使用查询型 context 时要重新确认相应索引能力是否仍为实验配置,见 Search Contexts。
先用关键词与正则收敛候选,再决定是否碰结构化搜索
Sourcegraph 5.4 起默认 pattern type 是 keyword;空格分隔的词是同一文档中的关键词交集,双引号是精确短语,/.../ 才把局部模式交给 RE2。若切换到 patterntype:regexp,整段模式按正则解释。RE2 不支持回溯与反向引用,因此面对嵌套括号、跨行调用和“两个捕获必须相同”时,不要把 PCRE 经验直接搬过来。
先在测试仓库放入下面两段调用,一段是真调用,一段只出现在注释里:
client.Do(ctx, legacy.Request{ID: id})
// client.Do(ctx, legacy.Request{ID: sample})第一轮用正则建立可审计的宽候选集:
lang:go repo:^gitlab\.example\.com/payments/ /client\.Do\([^\n]+legacy\.Request/ count:all预期两行都可能命中,因为全文索引只证明字节模式满足 RE2,并不理解注释或 Go 调用表达式。把“正则命中数”直接当迁移工作量会制造假阳性,但这个结果适合做候选盘点:查询稳定、可保存、能通过 Search Jobs 跑大集合,也符合官方当前推荐路径。
结构化搜索使用 Comby hole 与平衡分隔符匹配,更接近语法树形状,却不是编译器 AST,也不消费 SCIP 的符号解析。Sourcegraph 5.3 起默认关闭它,管理员必须显式设置:
{
"experimentalFeatures": {
"structuralSearch": "enabled"
}
}重载配置后,在专用测试仓库运行:
lang:go repo:^gitlab\.example\.com/payments/ client.Do(:[args]) patterntype:structural预期它能跨行并按平衡的 () 捕获参数;lang:go 会改变注释、字符串和分隔符的解释,不能省略后再宣称语言精确。反向实验是在未被 Zoekt 索引的功能分支运行同一结构化查询:即使动态全文搜索能读取该 revision,结构化搜索仍只支持 indexed repositories,因而可能零命中。更重要的是,官方明确说明它存在性能限制、没有积极开发,并建议改用正则或 Search Jobs 加自有脚本;Comby rule、缩进敏感代码块和 saved search 也有支持缺口。生产重构因此采用“关键词/正则盘点候选,语言 parser 或编译器在 workspace 内确认 AST,再生成 diff”的两阶段链路,不把 patterntype:structural 设成发布门禁。
关闭实验时从 site configuration 删除 experimentalFeatures.structuralSearch,重跑固定正则探针,确认团队保存的查询和自动化不再依赖结构化模式。配置回滚不会自动删除已经导出的结果或下游脚本,owner 还要清点这些衍生物。
索引搜索与动态搜索解决不同时间尺度
Zoekt 通常索引每个仓库的默认分支,从而让跨大量仓库的交互式搜索足够快。显式指定未索引的分支、标签或提交时,Sourcegraph 可以基于 Git 数据执行动态搜索:
repo:^gitlab\.example\.com/payments/ledger$ rev:release-next LegacyCharge这条查询的 repo: 锚定唯一仓库,rev: 改变搜索修订。若省略 rev:,默认搜索默认分支;所以默认分支零命中不能证明维护分支不存在调用。要检索多个修订,可以使用官方查询语法支持的修订列表或 glob,但跨所有分支的查询会扩大 Git 读取、任务数量与响应时间,应进入 Search Job 或离线影响分析,而不是塞进每次键入的全局查询。语法与当前限制以 Search Query Syntax 为准。
索引也有明确的语料缺口。官方 搜索配置 说明,默认会跳过二进制、无效 UTF-8、过大文件和超过 trigram 条件的文件;仓库根目录的 .sourcegraph/ignore 还可按提交排除路径。管理员可以在仓库 Settings → Indexing 查看跳过文件。search.largeFiles 能让特定大文本进入索引,但会增加索引大小、CPU、磁盘 I/O 和查询成本,且不能让无效 UTF-8 变成可索引文本。
做一个稳定的反向实验:在测试仓库功能分支加入唯一标记但不合并,先搜索默认分支,预期零命中;再加 rev:<功能分支>,预期命中。若第二步也为零,依次确认分支已被 gitserver 拉取、修订名正确、文件未被 .sourcegraph/ignore 排除、内容可作为文本读取。这个实验把“真的不存在”和“没有搜索那个修订”分开了。
需要把长期发布分支也纳入低延迟索引时,可在站点配置 experimentalFeatures.search.index.branches 中设置额外 indexed branches。该能力仍标为 Experimental,接入前要核对当前发布线;它会直接放大索引分片、更新工作和磁盘占用。先统计每个候选分支的代码体积、提交频率与实际查询量,再决定保留哪些长期分支。短命 feature branch 更适合动态搜索。
SCIP 把文本位置升级为符号关系
文本搜索能找到字符串,却不知道两个同名 Client 是否是同一个符号。Sourcegraph 的 Search-based Code Navigation 使用文本与语法启发式,可以开箱提供一部分跳转;Precise Code Navigation 则消费 SCIP 索引中的 document、occurrence、symbol 和 symbol information,把定义、引用、实现以及跨仓依赖关系绑定到具体提交。
SCIP 索引必须和仓库、提交、项目根目录匹配。最可靠的路径是在已有构建环境中运行语言索引器,因为那里已经具备依赖、生成代码、编译选项和私有包凭证。生成 index.scip 后安装 src 并上传:
export SRC_ENDPOINT=https://sourcegraph.example.com
export SRC_ACCESS_TOKEN="<CI_SECRET_STORE_INJECTS_TOKEN>"
<语言对应的 SCIP 索引器命令>
src code-intel upload \
-repo=gitlab.example.com/payments/ledger \
-commit="$CI_COMMIT_SHA" \
-root=services/ledger \
-file=index.scip-repo 必须等于 Sourcegraph 中的规范仓库名;-commit 绑定完整提交身份;-root 对齐 SCIP projectRoot,monorepo 子项目错一个目录就可能出现定义跳错或引用缺失;-file 指向生成物。不要在生产 CI 使用 -ignore-upload-failure 掩盖失败,否则流水线会绿色通过而导航悄悄退回搜索启发式。命令细节见 src code-intel upload。
预期证据不是“上传命令退出 0”这么简单。到同一提交打开符号,确认导航界面标识精确结果;先选择一个同仓跨文件引用。只有当语言索引器确实写出了外部符号关系、依赖仓库也有可关联的精确索引时,再选择一个跨仓依赖引用,核对仓库、提交和路径;单次上传不构成跨仓导航承诺。随后故意把 -commit 改为一个实例中不存在的提交或把 -root 改错,上传可能被拒绝、进入未关联状态,或无法在目标提交提供精确导航。这个反例证明 SCIP 数据不是脱离 Git 图独立生效的符号数据库。
Auto-indexing 适合标准构建;涉及私有依赖、复杂生成步骤或受控密钥时,官方建议在 CI 中索引。支持语言、索引器状态和功能矩阵会持续变化,接入时在 Precise Code Navigation 与 Indexer 列表 核对,不把 Beta 或 Experimental 状态写进生产承诺。
权限同步必须做“同查询、不同身份”实验
Sourcegraph 会缓存仓库代码,因此代码宿主在查询时临时拒绝访问并不能保护已经进入实例的副本。repository permissions 必须在 Sourcegraph 内部执行。官方 Permission syncing 描述了两条轮询链:以用户为中心同步其可读仓库,以仓库为中心同步可读用户;同步结果保存在内部数据库,调度器和任务队列并行更新。
这会产生一个真实窗口:代码宿主刚撤权,Sourcegraph 的权限同步尚未刷新。这里没有一个统一 TTL 开关;站点侧由 permissions.syncScheduleInterval、permissions.syncOldestUsers、permissions.syncOldestRepos、permissions.syncUsersBackoffSeconds、permissions.syncReposBackoffSeconds、permissions.syncUsersMaxConcurrency 与 permissions.syncReposMaxConcurrency 共同决定调度,代码宿主连接的 requestsPerHour 和外部 API 限流还会限制实际吞吐。大规模用户与仓库关系下,官方明确提醒同步可能耗时数小时。支持权限 webhook 的宿主应同时配置 webhook 来缩短撤权延迟。生产值来自离职回收 SLO、用户/仓库关系规模、代码宿主限流预算和完整同步周期实测,而不是复制示例数字。
上线前创建三个测试身份:
reader-a 同时能读公开测试仓库与私有测试仓库。reader-b 只能读公开测试仓库。site admin 仅用于管理验证,不参与普通用户基线,因为管理员会绕过仓库权限检查。
两名普通用户运行完全相同的唯一标记查询。预期 reader-a 得到两个仓库,reader-b 只得到公开仓库。随后在代码宿主撤销 reader-a 的私有仓库权限,触发或等待权限同步,再重复查询;预期私有结果消失。GraphQL 的 permissionsInfo.syncedAt 表示该用户或仓库自身方向最近一次同步,updatedAt 表示另一方向同步对它产生的最近更新,两者不能被误读成一个统一“权限已新鲜”时间。若普通用户仍能看到私有代码,立即停止开放,检查 authorization provider、用户身份映射、同步错误、连接 token 与用户 token 不一致,以及管理员误用。
在多宿主环境里,同一个邮箱并不天然等于同一个外部身份。SSO 登录、代码宿主账号关联、用户名规范化与授权 provider 必须形成唯一映射。使用 Explicit Permissions API 时,它会成为独立授权机制;初始空授权、站点管理员绕过和子仓路径规则都需要单独实验,见 Explicit permissions API。
数据驻留、敏感信息与审计不能留到采购最后一页
进入 Sourcegraph 边界的数据至少包括 Git 对象与工作树内容、仓库元数据、全文索引、SCIP 索引、代码宿主连接凭证、用户身份、权限关系、搜索查询和运行日志。搜索词本身可能包含尚未公开的漏洞编号、客户名、密钥片段或项目代号,因此日志平台的访问级别不能低于代码平台。
Cloud 方案要把实例区域、备份区域与保留、支持人员访问审批、日志导出、私网连接和删除证明写入数据处理评审;自托管方案要把 Git、Zoekt、PostgreSQL、Redis/队列、对象存储、备份和灾备副本逐一落到允许区域。网络上只允许实例访问批准的代码宿主 API、Git 端点、身份源、制品源和必要更新源,禁止把采集 token 放进部署仓库或 Compose 文件。
仓库排除不是立即擦除证明。退出一个敏感仓库时,先从所有代码宿主连接排除,等待 Repository Status 页面消失,再按部署形态确认 Git 镜像、索引、SCIP 上传、数据库元数据、日志和备份的保留策略。官方 移除仓库 提供了排除和损坏副本清理入口;生产操作要先备份并由平台 owner 执行,不能让应用团队直接删除 gitserver 数据目录。
容量与成本由“仓库数”之外的变量决定
容量估算至少输入仓库数、Git 数据体积、默认分支可索引文本量、额外 indexed branches、提交频率、并发用户、查询形状、权限关系规模、SCIP 索引量和 Search Job 并发。同样一万个仓库,许多小仓库与少数超大 monorepo 对分片、克隆、增量更新和尾延迟的影响完全不同。
Sourcegraph 的 Resource Estimator 只能作为初始估算。试运行要记录这些趋势:仓库同步积压是否回落、Zoekt 索引延迟是否稳定、索引磁盘是否随代码增长符合预测、查询 P95/P99 是否在代表性查询集上稳定、权限同步队列是否持续增长、SCIP 上传与处理是否堆积。扩容时分清瓶颈属于 gitserver 磁盘与网络、Zoekt 分片与页缓存、数据库、搜索编排,还是代码宿主 API 限流。
成本同样分层:托管或许可费用、计算、SSD 与备份、跨区流量、代码宿主 API 配额、SCIP 在 CI 中消耗的分钟数、平台值班与升级演练。扩大 search.largeFiles、索引所有发布分支、提高 Search Job 并发或提高权限同步频率都会改变其中至少一项。配置评审应要求“为什么增加、用什么指标证明有效、怎样撤回”。
从试点接入到长期治理
先选一组跨两个代码宿主、权限层次清晰、代码量可控的仓库。冻结唯一标记查询、非默认分支查询、忽略文件、SCIP 跨仓引用和双身份权限对照五组探针。每次版本升级、代码宿主 App 权限调整、索引配置变更和身份源迁移都重跑;失败时保留查询、用户、仓库、修订、结果数、任务 ID 与组件日志关联,不保存命中源码到低权限系统。
平台 owner 管部署、升级、备份、权限同步和容量;代码宿主 owner 管采集 App 与 API 限流;安全团队审批数据域和审计;语言平台 owner 维护 SCIP indexer 与构建镜像;应用 owner 维护 search context 和仓库内 .sourcegraph/ignore。任何一方都不能用“搜索能用”替代自己的证据。
升级采用固定发布线、小规模实例或隔离租户验证、备份恢复演练、再分批切流。索引可重建,不代表所有状态都可丢弃:站点配置、身份映射、授权状态、Batch Changes 元数据和审计证据要按官方升级与备份说明分类。回滚前确认数据库迁移是否支持目标版本,不能只回退容器镜像。
退出时先停止新增代码宿主连接与 SCIP 上传,导出必要的配置和审计证据,撤销采集 App、PAT 与 CI token,再按数据分类删除实例内副本、索引、数据库、对象存储和备份。最后用原来的跨宿主探针确认旧端点不可访问、代码宿主不再有活跃集成、CI 不再上传。这样 Sourcegraph 才从“方便的搜索框”变成一套可证明接入、可限制扩张、也可完整退出的代码基础设施。
