4. 版本控制与组学项目
组学项目与普通软件开发项目最大的差异之一,是它同时包含两类性质完全不同的资产:一类是文本型、体积小、需要频繁追踪变更的代码、配置、流程定义和文档;另一类是体积巨大、二进制、几乎不随代码逻辑变化而变化的测序数据、比对结果和中间文件。Git 的设计初衷是管理前者,如果直接把 FASTQ、BAM、VCF 或大型矩阵塞进仓库,仓库会在几次提交后膨胀到无法克隆、无法推送的程度。因此本节的核心不是“用不用 Git”,而是如何划定 Git 的边界,并让边界之外的大文件仍然可追溯、可复现。
一、哪些内容应该进入 Git
判断一个文件是否适合纳入版本控制,可以用三个问题快速筛选:它是否是文本或可读的结构化文本?它的体积是否在几十 KB 到几 MB 量级?它的变更是否代表分析逻辑或决策的变化?满足这三点的内容,通常都应该提交。
- 代码与脚本:R/Python 分析脚本、Shell 包装脚本、Snakemake/Nextflow 流程文件、自定义函数库。
- 配置与环境:
config.yaml、样本表samples.tsv、参考基因组版本声明、environment.yml、renv.lock、容器镜像的 digest 或 tag。 - 小数据与元数据:样本分组表、临床表型表、通路基因集(GMT)、引物序列、少量用于单元测试的迷你数据集。
- 文档:README、分析日志摘要、方法学说明、参数选择依据、结果解读记录。
这里有一个容易被忽视的细节:样本表本身是分析逻辑的一部分。如果样本分组、批次、配对关系写错,后续所有统计都会偏离。因此样本表必须纳入 Git,并且每次修改都应留下提交信息,例如“修正 S12 的配对编号,原记录与临床表不一致”。
二、大文件处理策略
大文件不进 Git,但必须解决“别人拿到仓库后如何获得同一份数据”的问题。常见策略有三类,选择取决于数据是否允许外传、是否有稳定存储位置以及团队规模。
| 策略 | 适用场景 | 关键做法 |
|---|---|---|
| 外部存储 + 校验和 | 原始测序数据、受控访问数据 | 数据放在对象存储或机构服务器,仓库中记录路径、MD5/SHA256 和获取方式 |
| Git LFS | 中等体积、需要版本追踪的二进制文件 | 用 git lfs track 声明模式,仓库只存指针,LFS 服务器存实体 |
| 数据下载脚本 | 公共数据(GEO、TCGA、Ensembl) | 提交下载脚本和版本号,运行时自动拉取并校验 |
以外部存储为例,可以在仓库中维护一个 data_manifest.tsv,字段包括文件相对路径、来源、版本、SHA256 和备注。这样即使数据不在仓库里,任何人也能根据清单确认自己拿到的是不是同一份数据。校验和是复现的“指纹”,没有它,路径相同也可能内容不同。
# data_manifest.tsv 示例(字段:path source version sha256 note)
raw/S1_R1.fastq.gz GEO:GSE000000 v1 a3f1...c9 原始测序数据,受控访问
ref/GRCh38.fa Ensembl release-110 b7d2...41 主参考基因组
meta/samples.tsv internal v3 — 样本分组与配对信息
三、忽略规则:让 Git 只看见该看见的
.gitignore 是边界的执行者。组学项目的忽略规则通常比普通项目更复杂,因为中间文件种类多、命名模式多。建议按“目录 + 扩展名 + 临时模式”三层组织,而不是零散地逐条添加。
# 大文件与中间结果
*.fastq
*.fastq.gz
*.bam
*.bai
*.vcf
*.vcf.gz
*.tbi
*.bigwig
*.bw
*.mtx
*.h5ad
*.rds
*.RData
# 分析输出目录
results/
logs/
tmp/
cache/
work/
# 环境与编辑器
.Rproj.user/
.Rhistory
.RData
__pycache__/
.ipynb_checkpoints/
.DS_Store
需要特别注意的是,.gitignore 只对“尚未被追踪”的文件生效。如果某个大文件已经被 git add 并提交过,再写忽略规则也不会把它移出历史。此时需要先用 git rm --cached 停止追踪,再提交;如果该文件已经进入历史且体积很大,则要考虑用 git filter-repo 重写历史,或直接重建仓库。重写历史会影响所有协作者,必须提前沟通。
git add . 一把梭。在组学项目中,这条命令极易把测序数据、临时文件和大型中间结果一起提交。更安全的做法是先 git status 确认待提交清单,再显式添加具体路径。四、提交粒度与可复现性
版本控制的价值不仅在于“存了”,更在于“能回到某个可解释的状态”。建议每次提交对应一个完整的、可描述的分析变更,例如“新增 FastQC 质控步骤并接入 config 中的 threads 参数”,而不是“改了一些东西”。提交信息中应尽量包含:改了什么、为什么改、影响哪些样本或结果。
对于配置与代码的联动,推荐把关键参数集中到 config.yaml,代码只读取配置而不硬编码。这样当参数变化时,Git 差异会直接显示在配置文件里,评审者一眼就能看出“这次分析把比对允许的错配数从 2 改成了 3”,而不是在几百行脚本里寻找改动。
五、一个可落地的最小工作流
把上述原则串起来,一个组学项目的日常版本控制动作可以归纳为:初始化仓库时先写 .gitignore 和 data_manifest.tsv;每次分析变更时,先改配置或脚本,再运行,确认结果合理后,只提交代码、配置、样本表和文档;大文件留在外部存储,并在清单中登记校验和。这样仓库始终轻量,而数据始终可定位、可验证。
.gitattributes,明确声明哪些扩展名走 LFS,避免成员各自配置不一致导致指针文件与实体文件混用。最后需要强调的是,版本控制不是“提交完就结束”。真正决定复现成败的,是提交内容是否完整描述了“用什么数据、什么参数、什么环境、得到什么结果”。Git 负责记录代码与配置的演化,清单与校验和负责锁定数据,环境锁定文件负责固定依赖。三者配合,组学项目才具备从仓库出发重新走通分析链路的基础。