跳转至

16. Git 版本控制 — 生信工程师必备技能

一句话说明: Git 是一个记录代码"谁在什么时候改了什么"的工具,让你随时可以回退、协作、不怕改坏代码。


一、核心概念(白话版)

1.1 什么是版本控制

白话比方: 你写毕业论文的时候,是不是经常"另存为"?

论文_v1.docx
论文_v2_导师修改.docx
论文_v3_终版.docx
论文_v3_终版_真的终版.docx
论文_v3_终版_打死不改了.docx

这就是手动版本控制——又乱又容易搞混。

版本控制系统(VCS, Version Control System) 就是自动帮你管理这些版本的工具。它会记录每次修改的内容、时间、作者,你随时可以"时光倒流"回到任何一个版本。

1.2 Git vs GitHub/GitLab 的区别

名称 是什么 白话比方
Git 安装在你电脑上的版本控制软件 本地的日记本,记录每次改动
GitHub 在线代码托管平台(微软旗下) 把日记本放到云端,别人也能看
GitLab 另一个在线代码托管平台 另一个云端,功能类似 GitHub

关键区别: Git 是工具,GitHub/GitLab 是平台。没有网络你也能用 Git,但 GitHub 需要联网。

1.3 三个工作区域

Git 有三个核心区域,理解它们是用好 Git 的关键:

工作区(Working Directory)  →  暂存区(Staging Area)  →  仓库(Repository)
     你正在改的文件              git add 后的文件           git commit 后的文件

白话比方: - 工作区 = 你的书桌,正在写的草稿纸 - 暂存区 = 信封,把写好的稿子装进去,准备寄出 - 仓库 = 邮局档案室,寄出去的信永久保存

1.4 分支(Branch)是什么

白话比方: 你写论文想试一个新的分析方法,但又怕搞砸现有的结果。

  • 没有分支: 复制一整份论文文件夹来改 → 改完还要手动合并
  • 有分支: Git 帮你创建一个"平行宇宙",你在新分支里随便改,改好了合并回来,改坏了直接删掉,主分支完全不受影响
main ────●────●────●────●────  (主线,稳定版本)
              \              /
 feature       ●────●────●     (新功能分支,试验用)

二、日常使用命令(最常用,重点掌握)

2.1 创建仓库:git init / git clone

# 方式一:在当前目录创建一个新的 Git 仓库(从零开始)
git init
# 白话:告诉 Git "从现在起帮我管这个文件夹"

# 方式二:从远程仓库克隆一份到本地(下载别人的项目)
git clone https://github.com/用户名/项目名.git
# 白话:把 GitHub 上的项目完整复制一份到你电脑上

# 使用场景:
# git init → 自己从零开始写一个新项目
# git clone → 下载别人的开源工具(如 MetaPhlAn、HUMAnN)

2.2 添加和提交:git add / git commit

# 查看当前状态(哪些文件改了、哪些是新的)
git status
# 白话:看看书桌上有哪些草稿还没装进信封

# 把文件添加到暂存区
git add analysis.py           # 添加单个文件
git add .                     # 添加当前目录所有改动的文件
git add scripts/              # 添加整个文件夹
# 白话:把写好的稿子装进信封

# 提交(保存一个版本快照)
git commit -m "添加物种丰度分析脚本"
# 白话:把信封寄出去,并在信封上写一句备注

# -m 后面是提交信息(commit message),描述你这次改了什么
# 好的提交信息:"fix: 修复Shannon指数计算公式中的除零错误"
# 差的提交信息:"改了一些东西" "update" "asdf"

# 使用场景:每完成一个小功能或修复就 add + commit 一次

2.3 同步远程:git push / git pull

# 把本地的提交推送到远程仓库(上传到 GitHub)
git push origin main
# 白话:把你本地写好的代码上传到 GitHub
# origin = 远程仓库的名字(默认叫 origin)
# main = 分支名(主分支)

# 从远程仓库拉取最新代码到本地(下载别人的更新)
git pull origin main
# 白话:把 GitHub 上别人的最新改动下载到你电脑上

# 使用场景:
# git push → 写完代码后上传备份,或者分享给同事
# git pull → 每天开始工作前先拉取最新代码

2.4 查看状态和历史:git status / git log / git diff

# 查看工作区状态
git status
# 白话:看看哪些文件改了但还没提交
# 红色 = 改了但没 add
# 绿色 = 已经 add 等待 commit

# 查看提交历史
git log                      # 完整日志
git log --oneline            # 精简版,每条提交只显示一行
git log --oneline -10        # 只看最近 10 条
git log --graph --oneline    # 图形化显示分支合并历史
# 白话:翻阅之前所有的"快递记录"

# 查看改动了什么内容
git diff                     # 查看工作区和暂存区的差异(还没 add 的改动)
git diff --staged            # 查看暂存区和仓库的差异(已经 add 但没 commit 的)
git diff HEAD~1              # 和上一次提交对比
# 白话:对比两份稿子,看看哪里不一样
# + 开头的行 = 新增内容(绿色)
# - 开头的行 = 删除内容(红色)

# 使用场景:提交前先 git diff 检查改动是否正确

2.5 分支操作:git branch / git checkout / git merge

# 查看所有分支(* 标记当前所在分支)
git branch
# 白话:看看有几条"平行时间线"

# 创建新分支
git branch feature-diversity
# 白话:开一条新的平行时间线叫 feature-diversity

# 切换到某个分支
git checkout feature-diversity
# 白话:跳到那条平行时间线上去工作

# 创建并立即切换(上面两步合一步)
git checkout -b feature-diversity
# 白话:开一条新时间线,同时跳过去

# 合并分支(先切回主分支,再合并)
git checkout main              # 先回到主分支
git merge feature-diversity    # 把 feature-diversity 的改动合并过来
# 白话:把平行宇宙的成果搬回主世界

# 删除已合并的分支
git branch -d feature-diversity
# 白话:用完的平行时间线可以关掉了

# 使用场景:
# 开发新功能 → 建分支 → 开发 → 测试通过 → 合并回 main → 删除分支

2.6 临时保存:git stash

# 场景:你正在写一半代码,突然需要切分支修 bug
git stash                    # 暂存当前未提交的改动
# 白话:把桌上写了一半的草稿先放抽屉里

git checkout main            # 切到其他分支去修 bug
# ... 修完 bug ...
git checkout feature-xxx     # 切回来继续工作

git stash pop                # 取出之前暂存的改动
# 白话:把抽屉里的草稿拿回桌上继续写

git stash list               # 查看所有暂存的内容
# 白话:看看抽屉里有几份草稿

2.7 忽略文件:.gitignore

# 在项目根目录创建 .gitignore 文件
# 里面写上不想被 Git 追踪的文件/文件夹

# .gitignore 文件内容示例:
*.log                # 忽略所有 .log 结尾的文件
*.pyc                # 忽略 Python 编译文件
__pycache__/         # 忽略 Python 缓存文件夹
.DS_Store            # 忽略 Mac 系统文件
data/raw/            # 忽略原始数据文件夹(太大了)
results/             # 忽略结果文件夹(可以重新生成)
.env                 # 忽略环境变量文件(可能有密码)

# 使用场景:把大文件、临时文件、敏感信息排除在版本控制之外

三、进阶操作

3.1 解决冲突(Merge Conflict)

什么时候会冲突? 两个人(或两个分支)同时改了同一个文件的同一行代码。

白话比方: 你和同事同时修改了同一份报告的同一段话,Git 不知道该听谁的,就报"冲突"让你手动选择。

冲突长什么样:

<<<<<<< HEAD
shannon_index = diversity(data, method="shannon")    # 你的版本
=======
shannon_index = calc_diversity(data, type="shannon") # 同事的版本
>>>>>>> feature-branch

解决步骤:

# 1. 合并时发现冲突
git merge feature-branch
# 输出:CONFLICT (content): Merge conflict in analysis.py

# 2. 打开冲突文件,手动选择保留哪个版本(或两者合并)
#    删掉 <<<<<<< ======= >>>>>>> 这些标记

# 3. 解决完后,标记为已解决
git add analysis.py

# 4. 完成合并提交
git commit -m "resolve: 合并 feature-branch 解决分析函数冲突"

3.2 git rebase vs git merge

特性 git merge git rebase
做什么 创建一个合并提交 把你的提交"搬到"目标分支最新位置后面
历史记录 保留完整分支历史(有分叉) 线性历史(没有分叉,更干净)
白话 两条路交汇 rebase 就像你写了 3 页作业,发现老师又发了新版讲义,你就把自己的 3 页撕下来重新贴到新讲义后面,看起来像一直基于最新版写的
推荐场景 团队协作(安全) 个人分支整理(干净)
注意 简单安全 不要对已推送的提交做 rebase
# merge(推荐新手使用)
git checkout main
git merge feature-branch

# rebase(让提交历史更干净)
git checkout feature-branch
git rebase main

3.3 git reset vs git revert

特性 git reset git revert
做什么 回退到某个版本,丢弃之后的提交 创建一个新提交来撤销某次改动
白话 时光倒流,后面的历史消失 用一个新的"反操作"来修正
会改历史吗 会(危险) 不会(安全)
推荐场景 本地还没推送的提交 已推送到远程的提交
# reset:回退到上一个版本(本地使用)
git reset --soft HEAD~1     # 回退提交但保留改动在暂存区
git reset --mixed HEAD~1    # 回退提交,改动放回工作区(默认)
git reset --hard HEAD~1     # 回退提交,改动全部丢弃(危险!)

# revert:安全地撤销某次提交(可以推送到远程)
git revert abc1234          # abc1234 是你要撤销的那次提交的 ID

3.4 tag 打标签

# 给重要版本打个标签(比如论文投稿时的代码版本)
git tag v1.0                         # 轻量标签
git tag -a v1.0 -m "论文投稿版本"     # 附注标签(推荐,有描述信息)

# 查看所有标签
git tag

# 推送标签到远程
git push origin v1.0         # 推送单个标签
git push origin --tags       # 推送所有标签

# 使用场景:论文投稿、pipeline 发布正式版本时打标签

四、生信项目中的 Git 最佳实践

4.1 哪些文件应该/不应该放进 Git

应该放(追踪) 不应该放(忽略)
脚本代码(.py .R .sh) 原始测序数据(.fastq .fq .bam .sam)
配置文件(config.yaml) 中间结果文件(.bai(BAM索引文件) .idx(通用索引文件))
小型注释文件(metadata.tsv) 大型数据库文件(.dmnd(DIAMOND比对数据库) .db(通用数据库文件))
文档说明(README.md) 临时文件(.tmp .log)
Snakefile / Nextflow 流程(生信流程管理工具,像自动化菜谱) 编译/缓存文件(pycache
环境配置(environment.yml) 敏感信息(密码、API key)

4.2 生信项目 .gitignore 模板

# ========== 测序数据(太大,不应追踪) ==========
*.fastq
*.fastq.gz
*.fq
*.fq.gz
*.bam
*.bam.bai
*.sam
*.sra
*.bcf
*.vcf.gz

# ========== 比对索引文件 ==========
*.bt2
*.fai
*.dict
*.amb
*.ann
*.bwt
*.pac
*.sa

# ========== 数据库文件 ==========
*.dmnd
*.nhr
*.nin
*.nsq

# ========== 中间/临时文件 ==========
*.tmp
*.log
*.o
*.e
slurm-*.out

# ========== Python ==========
__pycache__/
*.pyc
.ipynb_checkpoints/

# ========== R ==========
.Rhistory
.Rdata
.RData

# ========== 系统文件 ==========
.DS_Store
Thumbs.db

# ========== 环境/敏感信息 ==========
.env
credentials.json

# ========== 大型结果(可选择性忽略) ==========
# results/figures/ 可以追踪(方便查看)
# results/tables/  可以追踪
# results/intermediate/ 不追踪

4.3 commit message 规范

推荐格式: 类型: 简短描述

feat: 添加Alpha多样性计算模块         # 新功能
fix: 修复Shannon指数公式除零错误       # 修 bug
docs: 补充README中的使用说明           # 文档
refactor: 重构数据清洗函数             # 重构代码(把代码重新整理一遍,功能不变——像整理杂乱书房,书还是那些书)
data: 更新样本元数据表                 # 数据变更
style: 统一代码缩进为4空格             # 代码格式
test: 添加分类器准确率测试             # 测试
chore: 更新conda环境配置              # 杂项

4.4 生信 pipeline 版本管理

# 1. 用 tag 标记 pipeline 的稳定版本
git tag -a v2.0 -m "MetaG pipeline v2.0: 支持PE150数据"

# 2. 用 environment.yml 锁定软件版本
conda env export > environment.yml
git add environment.yml
git commit -m "chore: 锁定conda环境软件版本"

# 3. 在论文/报告中注明代码版本
# "分析使用 pipeline v2.0 (commit: abc1234)"

五、常用 Git 工作流

5.1 个人开发流程

写代码 → git add → git commit → git push → 继续写 → 循环

1. 写/改代码
2. git status          # 看看改了什么
3. git diff            # 确认改动内容
4. git add .           # 添加到暂存区
5. git commit -m "描述" # 提交
6. git push            # 推送到 GitHub 备份

5.2 团队协作(Feature Branch Workflow)

流程图(文字版):

main 分支(稳定版本,所有人共享)
  │
  ├── git checkout -b feature-diversity  (开个新分支写新功能)
  │        │
  │        ├── 写代码 → add → commit(在分支上反复开发)
  │        ├── 写代码 → add → commit
  │        │
  │        ├── git push origin feature-diversity(推到远程)
  │        │
  │        └── 在 GitHub 上提 Pull Request(PR,就是"我写好了,请大家检查一下再合并"——像交作业前先找同学检查)
  │                 │
  │                 ├── 同事 Code Review(代码审查)
  │                 ├── 讨论 / 修改
  │                 └── 审核通过 → Merge 合并到 main
  │
  ├── 下一个功能:git checkout -b feature-xxx(再开新分支)
  │   ...

核心原则: - main 分支永远保持可用状态 - 每个新功能/修复在独立分支上开发 - 通过 Pull Request 合并,团队成员互相审查代码


六、实操演练:完整流程

以下是一个完整的从零开始的 Git 使用流程:

# ==========================================
# 第一步:初始化项目
# ==========================================
mkdir t2d_metagenome              # 创建项目文件夹
cd t2d_metagenome                 # 进入文件夹
git init                          # 初始化 Git 仓库

# ==========================================
# 第二步:配置用户信息(首次使用需要设置)
# ==========================================
git config --global user.name "Wenqiang Peng"
git config --global user.email "你的邮箱@example.com"

# ==========================================
# 第三步:创建项目文件
# ==========================================
mkdir scripts data results docs   # 创建子目录
touch README.md                   # 创建说明文件
touch scripts/01_qc.sh            # 创建质控脚本
touch .gitignore                  # 创建忽略规则文件

# ==========================================
# 第四步:设置 .gitignore
# ==========================================
echo "*.fastq.gz" >> .gitignore
echo "*.bam" >> .gitignore
echo "data/raw/" >> .gitignore
echo "results/intermediate/" >> .gitignore

# ==========================================
# 第五步:首次提交
# ==========================================
git add .                          # 添加所有文件到暂存区
git status                         # 确认状态
git commit -m "feat: 初始化T2D宏基因组项目结构"

# ==========================================
# 第六步:写代码,开发功能
# ==========================================
# (编辑 scripts/01_qc.sh 添加质控代码)
git add scripts/01_qc.sh
git commit -m "feat: 添加fastp质控脚本"

# ==========================================
# 第七步:连接远程仓库并推送
# ==========================================
# 先去 GitHub 创建一个同名空仓库
git remote add origin https://github.com/你的用户名/t2d_metagenome.git
git branch -M main                 # 把主分支重命名为 main
git push -u origin main            # 首次推送(-u 设置默认上游分支)

# ==========================================
# 第八步:分支开发
# ==========================================
git checkout -b feature-diversity  # 创建并切换到新分支
# (编辑代码...)
git add .
git commit -m "feat: 添加Alpha多样性分析脚本"
git push origin feature-diversity  # 推送分支到远程

# 在 GitHub 上创建 Pull Request,审核通过后合并

# ==========================================
# 第九步:合并后清理
# ==========================================
git checkout main                  # 切回主分支
git pull origin main               # 拉取合并后的最新代码
git branch -d feature-diversity    # 删除已合并的分支

# ==========================================
# 第十步:打标签发布
# ==========================================
git tag -a v1.0 -m "T2D pipeline v1.0 完成质控和多样性分析"
git push origin --tags             # 推送标签到远程

七、面试怎么答

Q1: 请简单介绍一下 Git 是什么?你在项目中怎么用的?

白话回答:

Git 是一个版本控制工具,可以记录代码的每一次修改。我在宏基因组项目中用 Git 管理分析脚本和流程文件。每完成一个分析步骤就 commit 一次,commit message 写清楚改了什么。代码同步推送到 GitHub 做备份。这样做的好处是:第一,代码改坏了可以回退;第二,导师和同事可以看到我的分析过程;第三,论文投稿时可以打 tag 标记对应的代码版本,保证可复现。

Q2: git merge 和 git rebase 有什么区别?

白话回答:

两者都是合并分支的方法。merge 会保留完整的分支历史,生成一个合并提交,可以看到"哪条分支什么时候合进来的"。rebase 会把你分支上的提交"搬到"目标分支最新位置后面,历史是一条直线,更干净。我个人习惯是:团队协作用 merge(安全、有记录),个人整理提交用 rebase(干净)。有一条铁律:已经推送到远程的提交不要 rebase,否则会把队友搞崩溃。

Q3: git reset 和 git revert 有什么区别?

白话回答:

reset 是"时光倒流",直接回退到某个版本,后面的提交历史都没了。revert 是"做一个反向操作",创建一个新提交来撤销之前的改动,但历史记录还在。如果代码还没推送到远程,用 reset 没问题。如果已经推送了,只能用 revert,因为 reset 会改变历史,导致别人的代码对不上。

Q4: 生信项目中哪些文件应该放进 Git?哪些不应该?

白话回答:

应该放的是:分析脚本(Python、R、Shell)、流程文件(Snakefile)、配置文件、conda 环境配置、README 文档。不应该放的是:原始测序数据(.fastq 几个 G 太大了)、比对文件(.bam)、大型数据库文件。这些大文件通过 .gitignore 排除掉。如果真的需要版本管理大文件,可以考虑 Git LFS(Large File Storage,把大文件存在专门服务器上,仓库里只保留一个链接,仓库不会被大文件撑爆)。

Q5: 你能描述一下 Feature Branch Workflow 吗?

白话回答:

这是团队协作最常用的 Git 工作流。核心思路是:main 分支始终保持稳定可用,每个新功能或 bug 修复都在独立的 feature 分支上开发。流程是:从 main 拉一个新分支,在上面写代码并提交,写完推到远程,然后在 GitHub 上提 Pull Request。团队成员做 Code Review,审核通过后合并到 main,最后删掉 feature 分支。这样做的好处是不会互相干扰,代码质量也有保障。

Q6: 遇到 merge conflict 怎么解决?

白话回答:

冲突是因为两个人同时改了同一个文件的同一行。Git 会在文件里用特殊标记标出冲突的部分,HEAD 下面是你的版本,等号下面是对方的版本。解决方法是:打开文件,手动选择保留哪个版本(或者两者合并),删掉那些标记符号,然后 git add 标记为已解决,最后 git commit 完成合并。

Q7: git fetch 和 git pull 有什么区别?

白话回答:

git fetch 是只从远程仓库下载最新的数据,但不会自动合并到你当前的代码里,相当于"看看远程有什么更新"。git pull 等于 git fetch + git merge,下载完直接合并。我个人习惯是如果不确定远程有什么改动,先 fetch 看看再决定要不要合并,更安全一些。


八、延伸阅读

  1. Pro Git 中文版(免费电子书) — 最权威的 Git 教程
  2. 官方网站:https://git-scm.com/book/zh/v2
  3. PLOS 论文:A Quick Introduction to Version Control with Git and GitHub — 生物学家视角的 Git 入门
  4. DOI: 10.1371/journal.pcbi.1004668
  5. Atlassian Git Tutorial — 配图精美的 Git 教程(英文)
  6. https://www.atlassian.com/git/tutorials
  7. Learn Git Branching — 交互式学习 Git 分支操作的网站
  8. https://learngitbranching.js.org/?locale=zh_CN
  9. GitHub Git Cheat Sheet — GitHub 官方 Git 速查表
  10. https://education.github.com/git-cheat-sheet-education.pdf
  11. Oh Shit, Git!?! — Git 常见问题急救指南
  12. https://ohshitgit.com/zh

最后提醒: Git 不需要一次学完所有命令。日常开发掌握 add → commit → push → pull 这四个命令就够用了。遇到问题再查其他命令,用多了自然就熟了。