科研代码很少会直接保持在“最终版”。改一个参数、换一份数据、补一个评价指标,结果可能就变了。几个月后再回头看,难找的往往是这张图究竟来自哪版代码、哪组配置,以及中间改过什么。
Git 解决的正是这类版本和协作问题。它既可以管理一个人反复试验的过程,也可以让多位合作者在同一个项目中留下可检查的修改记录。
现在,Codex、Claude Code、GitHub Copilot 这类工具已经可以读取仓库上下文并同时修改多个文件。代码改得更快以后,版本边界反而更值得重视:人需要看清 AI 改了什么、哪些修改准备保留;AI 在被允许读取仓库信息时,也能借助清楚的目录、差异和提交历史理解当前项目。以 VS Code 为例,其 workspace context 文档 已经把整个工作区和代码搜索作为智能体获取上下文的重要来源。
Summary
对科研项目来说,Git 最实用的价值有四个:记录代码和配置变化、隔离不同实验、追踪协作过程、把结果对应到明确版本。
科研代码最容易丢失的是上下文
不少科研项目都出现过类似的文件:
plot.py
plot_new.py
plot_final.py
plot_final2.py
plot_final_修改.py这套方法应用在少量文件时还行得通,但文件量一多时间一长就会遇到几个问题:
- 文件名只能表达很少的信息,很难说明具体改了哪些内容;
- 代码、配置和说明文档各自复制,版本之间容易错位;
- 删除过的逻辑很难找回,也不知道为什么删除;
- 多个人(多个来源)同时修改时,只能人工比较多个文件;
- 论文返修或结果复查时,很难还原当时的运行状态。
Git 会把一次提交保存为项目状态的一个快照,并让后续提交指向前一个提交。大多数历史查看、比较和提交操作都可以在本地完成,不依赖持续联网。这个设计使代码变化形成了一条可追踪的历史,而不是散落在多个“最终版”文件里。Pro Git 对 Git 数据模型的说明 中将其概括为一系列项目快照。
这里需要提前说清:Git 的核心任务是版本控制。远程仓库、服务器快照和异地备份仍然要单独安排。没有提交的修改也可能丢失,所以“装了 Git”本身不会自动保护文件。
从一个项目认识 Git
为了把这些情况讲得具体一些,我准备了一个小型气象项目示例。它读取粗网格温度、网格海拔和站点观测,使用温度垂直递减率做订正,再计算 MAE、RMSE,并输出观测—预测对比图。
仓库中主要有这些内容:
configs/ 模型和路径配置
data/raw/ 原始数据
tests/ 模型与指标测试
weather_downscale_demo/ 数据处理、模型、评估与绘图
outputs/ 本地生成结果
pyproject.toml Python 项目配置
uv.lock 依赖锁定结果项目使用 uv 管理 Python 环境,这里推荐在构建新 python 项目时优先使用 uv 管理。拿到仓库后可以直接运行 uv sync 会根据项目配置和锁文件同步环境,具体行为可以参考 uv 官方文档。

从初始化到第一次提交
安装 Git 后先确认命令可用,并设置提交身份:
git --version
git config --global user.name "Your Name"
git config --global user.email "you@example.com"用户名和邮箱会进入提交记录。公开仓库如果不希望展示常用邮箱,可以使用代码托管平台提供的匿名邮箱,但不要随便填写一个无法区分作者的公共身份。
学会看仓库状态
Git 的日常使用可以先记住两条命令:
git status
git diffgit status 会区分已经跟踪、尚未跟踪、已经暂存和仍在工作区中的变化;

git diff 用来查看具体改动了哪些行。

当然, 不学命令也完全没问题! VS Code 中暂存、提交、创建分支、查看历史和解决冲突都可以在图形界面完成。对刚接触 Git 的用户来说,先理解状态变化,再使用界面操作,通常比记住大量命令更顺手。
在 vscode 侧边栏打开“源代码管理”,可以看到代码相对于上次提交后的最新变动(也可以在目录中直接看到标记)

示例中有多种未提交状态,它们都只存在于工作区,尚未提交:
D 表示一个已跟踪文件被删除
M 表示文件内容发生修改
U 表示新文件还没有进入 Git 管理在 vscode 中能看到具体的新增行、删除行和修改位置。通常绿色表示新增,红色表示删除,黄色表示修改。

或者在修改对照界面查看,更直观

分清工作区与暂存区
在仓库中的每次修改,其内容会先保存在工作树中。点击 VS Code 左侧的“源代码管理”,这些还没有暂存的文件会出现在 Changes 区域。此时只是本机文件发生了变化,Git 历史里还没有新的版本。
Git 的几个区域很容易混在一起,可以先按下面这条路径理解:
工作区 / 工作树
正在编辑的项目文件
↓ 暂存
暂存区(Index / Staging Area)
下一次提交准备包含的内容
↓ 提交
本地仓库(.git)
提交历史、分支、标签等对象
↓ 推送 / 同步
远程仓库
GitHub、GitLab 或课题组服务器上的副本“工作区”通常指 VS Code 当前打开、可以直接编辑的项目目录;Git 官方术语更常写作 working tree(工作树),也就是从某个提交检出到磁盘上的那套文件。大多数普通仓库只有一棵工作树,所以这两个说法经常被混用。
“仓库”一般指整个项目文件夹。严格来说,本地仓库的历史和对象主要保存在 .git 中,工作树是从这份历史中检出的可编辑文件。Git Glossary 对 working tree、index、repository 和 HEAD 有更严格的定义。
暂存区并不是另一个需要手动寻找的文件夹。它保存的是“下一次提交准备记录的快照”。在 VS Code 中,点击文件右侧的 +,文件就会从 Changes 移到 Staged Changes。点击 - 只是取消暂存,文件修改仍然保留。

最后还有一个经常出现的词:HEAD。它可以理解为“当前工作位置”,通常指向正在使用的分支及其最新提交。切换分支以后,HEAD 会跟着移动,工作树也会更新为对应分支的内容。
一个文件为什么会同时出现在两处
这是暂存区最值得单独解释的情况。
假设先修改 configs/default.toml,随后把它加入暂存区。此时暂存区保存了这个文件的当前版本。如果继续编辑同一个文件,新的修改会再次出现在 Changes(更改),而刚才暂存的内容仍然留在 Staged Changes(暂存的更改)。

于是,同一个文件名可能同时出现两次:
- Staged Changes:已经选入下一次提交的部分;
- Changes:暂存以后又产生的新修改。
这并不是重复文件。Git 正在同时保存两个不同状态。点击提交时,默认只记录暂存区中的内容,后续修改会继续留在工作树。
对于科研代码,这个能力很实用。一个脚本里可能同时包含“修复单位错误”和“尝试新的配色”。可以只暂存单位修复,先形成一个容易审查的提交,绘图试验留到下一次。
完成一次清楚的提交
将一次修改的文件添加到暂存区后,就可以正式提交到仓库了,提交时要求必须要有说明,不想每次手动写的话可以让 VS Code 根据暂存内容自动生成提交信息。

提交信息应说明这次修改的目的,例如“修正温度递减率方向”会比“更新代码”更有用。一个提交尽量只表达一个完整意图。“增加高海拔配置”和“重写验证图”最好拆开。以后查看历史、撤销修改,或者让 AI 分析某次变化时,边界清楚的提交会更容易理解。
这里还要区分两个看起来相近的操作:
- Unstage Changes / 取消暂存:只把内容移回 Changes,不会删除修改;
- Discard Changes / 放弃更改:会丢弃尚未提交的修改,使用前必须确认。
Warning
暂存和取消暂存通常可逆;放弃更改会直接影响工作树。界面按钮很方便,但仍要先看清操作对象。
用分支隔离科研实验
科研代码经常需要同时保留一条稳定基线和几条探索路线。比如主分支已经能生成温度订正结果,现在想增加湿度订正、替换损失函数,或测试一套新的站点筛选规则。分支可以让这些尝试沿着不同历史向前推进。
在 VS Code 中,当前分支会显示在底部状态栏和 Source Control Graph 中。点击分支名,或在命令面板选择 Git: Create Branch,就可以从当前版本建立实验分支。实验完成后,再从 Git: Merge Branch 选择需要合并的分支。
对科研项目,可以考虑这样区分:
main或 master:可以运行、通过基本检查的稳定版本;exp/…:结论还不确定的试验;feature/…:目标明确、准备合并的功能;fix/…:针对具体问题的修复。
分支名称只是一种约定,重点是让其他人一眼知道这条线处于什么状态。当然这并不是必须的,对于我们自己的仓库完全可以一条线走到尾

在这里推荐使用 VS Code 中 Git 管理的插件,可以提供更美观、功能更强大的界面:
- GitLens

- Git Graph

版本冲突时 Git 会提醒你处理
两条分支修改了同一行、一个分支删除文件而另一个分支继续修改,Git 都可能暂停合并并要求人工处理。冲突表示自动合并缺少足够信息,项目本身并没有因此损坏。
VS Code 会把冲突文件列在 Merge Changes 中。简单冲突可以选择 Current、Incoming 或 Both;复杂情况更适合打开三方合并编辑器,同时查看当前分支、传入分支和最终结果。
当前 VS Code 还提供实验性的 AI 冲突解决入口。AI 可以分析共同祖先和两边变化并给出候选结果,但最终版本仍要由人检查。物理含义、数据口径和实验边界往往不在冲突代码片段里。
多人协作时要区分本地和远程
一次 Commit 只会进入本地仓库。点击 Push 或 Sync Changes 后,提交才会发送到 GitHub(或其它远程仓库)。远程仓库负责交换代码和保存共享历史,本地工作树仍然可以离线修改和提交。
当历史真正有用
Git 的优势不只体现在“保存过几个版本”。当出现以下可能的情况时,Git 提交历史会变成排查和复现的证据链,这里不过多赘述:
- 找到问题从哪次修改开始
- 已经共享的错误需要撤销
- 临时工作和阶段版本随时保存和回顾
Git 管理科研项目的边界
Git 很适合保存脚本、配置、文档、小型测试数据和环境锁文件。对于 WRF 或 AI 降尺度项目,下面这些内容通常值得进入仓库:
- 数据预处理、训练、评估和绘图脚本;
- namelist、YAML、TOML 等配置文件;
pyproject.toml、uv.lock、environment.yml等环境文件;- README、实验索引等说明文件(这就体现出 markdown 文件的优势了)。
大型 NetCDF、 Zarr、模型输出以及生成的图片文件需要排除在外。它们会让仓库迅速膨胀,也不适合频繁提交。确实需要随仓库管理大文件时,可以评估 Git LFS;它在 Git 中保存指针,把实际大文件放在远程存储。
Git 仓库通过 .gitignore 将不需要追踪的文件排除,例如:
__pycache__/
outputs/
*.nc
*.png令牌、密码、私钥、内部服务器地址和未公开数据也不要进入提交。
Warning
上传仓库或把项目交给 AI 处理前,检查数据授权、个人信息、内部路径和合作协议。公开代码不代表所有输入与结果都适合公开。
常用命令和易混淆概念
VS Code 已经覆盖日常 Git 操作,命令行仍然适合快速检查、远程服务器和故障排查。下面保留一份小型速查,不需要一次全部记住。
常用命令速查
| 目的 | 命令 |
|---|---|
| 查看仓库状态 | git status |
| 查看尚未暂存的逐行变化 | git diff |
| 查看下一次提交将包含什么 | git diff --staged |
| 暂存指定文件 | git add <file> |
| 创建提交 | git commit -m "说明修改目的" |
| 查看图形化历史 | git log --oneline --graph --decorate --all |
| 更新远程状态但不修改当前分支 | git fetch |
| 推送本地提交 | git push |
| 临时收起修改 | git stash push -u -m "WIP: 说明" |
| 撤销一个已提交的修改 | git revert <commit> |
| 在好坏版本间定位错误 | git bisect start <bad> <good> |
最容易混淆的概念
| 概念 | 区别 |
|---|---|
| 工作区与工作树 | 日常语境里常指同一套可编辑文件;严格术语中 working tree 是检出的文件,worktree 还包含独立的 HEAD、暂存区等元数据 |
| 暂存与提交 | 暂存是在选择下一次提交的内容;提交才会在本地历史中形成新快照 |
| Commit 与 Push | Commit 写入本地仓库;Push 才把本地提交发送到远程 |
| Unstage 与 Discard | Unstage 只取消暂存;Discard 会丢弃工作树修改 |
| Fetch 与 Pull | Fetch 只更新远程信息;Pull 会把远程变化继续整合到当前分支 |
| Restore 与 Revert | Restore 主要处理工作树或暂存区文件;Revert 用新提交撤销已有提交 |
| Branch 与 Worktree | Branch 是指向提交的分支引用;Worktree 是把某条分支检出到独立目录后的工作实例 |
| Stash 与 Commit | Stash 适合短期收起未完成修改;Commit 适合长期、可解释的项目历史 |
| Merge 与 Rebase | Merge 保留两条历史的汇合;Rebase 会重放提交并改变提交基点,共享分支上需要更谨慎 |
回到 AI 辅助编程这个背景,Git 的价值还在继续增加。AI 可以在短时间内改动更多文件,人需要用状态、差异、暂存和提交边界来审查这些变化;清楚的 Git 历史也能为后续的人和 AI 提供更可靠的仓库上下文。无论代码由谁写,最终都应该能回答:改了什么、为什么改、验证过什么,以及这组结果对应哪个版本。