1.2.4 分支与协作

这一节解释 Git 为什么能支持安全协作。你会理解分支如何让你在不破坏主线代码的情况下尝试新功能,并初步认识 Pull Request 和合并冲突,为以后参与团队项目和开源贡献做准备。
- 理解分支的概念和使用场景
- 掌握创建、切换、合并分支的操作
- 了解 Pull Request 的协作流程
- 学会解决简单的合并冲突
什么是分支?
Section titled “什么是分支?”用装修来类比
Section titled “用装修来类比”想象你住在一间公寓里(main 分支 = 你正在住的家)。你想尝试一种新的装修风格,但不确定效果好不好。
你有两个选择:
- 直接在家里改——如果改坏了,你就没得住了
- 先租一间一模一样的公寓(新分支),在那边尝试——如果好看就搬过来,不好看就退租
分支就是选项 2。你在新分支上随便改,改好了合并回 main,改坏了直接删掉,main 完全不受影响。
在代码中的实际场景
Section titled “在代码中的实际场景”你正在做一个 AI 图像分类项目,main 分支上是能正常运行的代码。
现在你想尝试:
- 把模型从 CNN 换成 Vision Transformer。
- 验证效果是不是真的更好。
- 花几天时间修改可能涉及很多文件的内容。
如果直接在 main 上改,半成品代码可能跑不起来,紧急 bug 修复也很难发布,最后发现 ViT 不合适时还可能要回退几十个文件。
如果用分支,你可以在 feature/vit 上慢慢试;突然要修 bug,就切回 main 处理;实验失败时,直接删掉这个分支即可。
分支基本操作
Section titled “分支基本操作”# 查看本地分支(当前分支前面有 * 号)git branch# 输出:# * main
# 查看所有分支(包括远程)git branch -a创建并切换分支
Section titled “创建并切换分支”# 创建一个新分支git branch feature/data-augmentation
# 切换到新分支git checkout feature/data-augmentation
# 或者一步到位:创建并切换(更常用)git checkout -b feature/data-augmentation案例:在分支上开发新功能
Section titled “案例:在分支上开发新功能”让我们实际操作一下。继续使用之前的 ai-image-classifier 项目:
cd ai-image-classifier
# 确认当前在 main 分支git branch# * main
# 创建并切换到新分支:添加数据增强功能git checkout -b feature/data-augmentation现在你在新分支上了。开始写代码:
# 创建数据增强模块cat > src/augmentation.py << 'EOF'import torchvision.transforms as T
def get_train_transforms(): """训练数据的增强策略""" return T.Compose([ T.RandomHorizontalFlip(p=0.5), # 50% 概率水平翻转 T.RandomRotation(degrees=15), # 随机旋转 ±15 度 T.ColorJitter( # 颜色抖动 brightness=0.2, contrast=0.2, saturation=0.2 ), T.ToTensor(), T.Normalize( mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225] ), ])
def get_test_transforms(): """测试数据只做标准化,不做增强""" return T.Compose([ T.ToTensor(), T.Normalize( mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225] ), ])EOF
# 更新 train.py,使用数据增强cat >> src/train.py << 'EOF'
# 新增:使用数据增强from augmentation import get_train_transforms, get_test_transformstrain_transform = get_train_transforms()test_transform = get_test_transforms()print("数据增强策略已加载")EOF
# 提交到当前分支git add .git commit -m "feat: 添加数据增强模块(随机翻转、旋转、颜色抖动)"现在查看两个分支的状态:
# 查看当前分支的历史git log --oneline -3# 输出:# aaa1111 feat: 添加数据增强模块(随机翻转、旋转、颜色抖动)# bbb2222 完善 README:添加项目说明和使用方法# ccc3333 添加 .gitignore
# 切回 main 看看git checkout main
# main 上没有 augmentation.py!ls src/# model.py train.py utils.py (没有 augmentation.py)
# 切回 feature 分支git checkout feature/data-augmentationls src/# augmentation.py model.py train.py utils.py (有了!)这就是分支的魔力——两条时间线互不影响。
当你在分支上的功能开发完成、测试通过后,就可以把它合并回 main。
# 第一步:切回 main 分支git checkout main
# 第二步:把 feature 分支合并到 maingit merge feature/data-augmentation输出:
Updating bbb2222..aaa1111Fast-forward src/augmentation.py | 25 +++++++++++++++++++++++++ src/train.py | 5 +++++ 2 files changed, 30 insertions(+) create mode 100644 src/augmentation.py现在 main 分支也有数据增强代码了:
ls src/# augmentation.py model.py train.py utils.py ✅合并后的清理
Section titled “合并后的清理”# 功能分支已经合并,可以删掉了(保持仓库整洁)git branch -d feature/data-augmentation
# 查看分支——只剩 maingit branch# * main
什么时候会冲突?
Section titled “什么时候会冲突?”当两个分支修改了同一个文件的同一个位置,Git 不知道该保留哪个版本,就会产生冲突。
案例:制造一个冲突并解决它
Section titled “案例:制造一个冲突并解决它”# 从 main 创建两个分支,模拟两个人同时工作git checkout -b alice/update-modelcat > src/model.py << 'EOF'import torchimport torch.nn as nn
class SimpleCNN(nn.Module): def __init__(self): super().__init__() self.conv1 = nn.Conv2d(3, 32, 3, padding=1) # 工程师 A: 改成 32 个滤波器 self.pool = nn.MaxPool2d(2, 2) self.fc1 = nn.Linear(32 * 16 * 16, 10)
def forward(self, x): x = self.pool(torch.relu(self.conv1(x))) x = x.view(-1, 32 * 16 * 16) return self.fc1(x)EOFgit add . && git commit -m "alice: 增加滤波器数量到 32"
# 切回 main,创建 bob 的分支git checkout maingit checkout -b bob/update-modelcat > src/model.py << 'EOF'import torchimport torch.nn as nn
class SimpleCNN(nn.Module): def __init__(self): super().__init__() self.conv1 = nn.Conv2d(3, 64, 5, padding=2) # 工程师 B: 改成 64 个滤波器,5x5 卷积核 self.pool = nn.MaxPool2d(2, 2) self.fc1 = nn.Linear(64 * 16 * 16, 10)
def forward(self, x): x = self.pool(torch.relu(self.conv1(x))) x = x.view(-1, 64 * 16 * 16) return self.fc1(x)EOFgit add . && git commit -m "bob: 改用 64 个滤波器和 5x5 卷积核"现在合并 工程师 A 的修改:
git checkout maingit merge alice/update-model # ✅ 成功,无冲突再合并 工程师 B 的修改:
git merge bob/update-model# 输出:# CONFLICT (content): Merge conflict in src/model.py# Automatic merge failed; fix conflicts and then commit the result.冲突了! 因为 工程师 A 和 工程师 B 都修改了 model.py 的同一行。
打开 src/model.py,你会看到 Git 标记出了冲突的位置:
class SimpleCNN(nn.Module): def __init__(self): super().__init__() # 工程师 A 保留 3x3 卷积核,把滤波器改成 32 个。 self.conv1 = nn.Conv2d(3, 32, 3, padding=1) self.fc1 = nn.Linear(32 * 16 * 16, 10)
# 工程师 B 同时修改滤波器数量和卷积核大小。 self.conv1 = nn.Conv2d(3, 64, 5, padding=2) self.fc1 = nn.Linear(64 * 16 * 16, 10)- 真实冲突里,Git 会先显示
<<<<<<< HEAD,然后是当前分支版本,再显示=======,然后是要合并进来的版本,最后用>>>>>>> branch-name收尾。 - 上面的示例改用
CONFLICT_MARKER_*占位符,是为了避免仓库检查把教学示例误判成未解决的真实冲突。
你需要手动决定最终要保留什么。 比如我们决定采用 工程师 B 的方案:
import torchimport torch.nn as nn
class SimpleCNN(nn.Module): def __init__(self): super().__init__() self.conv1 = nn.Conv2d(3, 64, 5, padding=2) # 采用 工程师 B 的方案 self.pool = nn.MaxPool2d(2, 2) self.fc1 = nn.Linear(64 * 16 * 16, 10)
def forward(self, x): x = self.pool(torch.relu(self.conv1(x))) x = x.view(-1, 64 * 16 * 16) return self.fc1(x)把 <<<<<<<、=======、>>>>>>> 标记全部删掉,只留下你想要的代码。然后:
git add src/model.pygit commit -m "merge: 合并 工程师 A 和 工程师 B 的修改,采用 工程师 B 的 64 滤波器方案"冲突解决了。
# 清理分支git branch -d alice/update-modelgit branch -d bob/update-modelPull Request(了解即可)
Section titled “Pull Request(了解即可)”在团队协作中,你通常不会直接往 main 分支合并。而是通过 Pull Request(PR) 让别人先审查你的代码,确认没问题后再合并。
Pull Request 的流程
Section titled “Pull Request 的流程”1. 你创建一个 feature 分支,写代码2. push 到 GitHub3. 在 GitHub 上创建 Pull Request4. 同事审查你的代码,提出修改建议5. 你根据建议修改,push 新的提交6. 同事点击 "Approve"(通过)7. 代码被合并到 main 分支# 1. 创建分支并写代码git checkout -b feature/add-evaluationecho "def evaluate(model, dataloader): pass" > src/evaluate.pygit add . && git commit -m "添加模型评估模块"
# 2. 推送分支到 GitHubgit push -u origin feature/add-evaluation然后打开 GitHub,你会看到一个提示:
feature/add-evaluation had recent pushes — Compare & pull request
点击这个按钮,填写 PR 的标题和描述,点击 Create pull request 就完成了。
对于个人项目,你可以自己审查后直接在 GitHub 页面上点 Merge pull request 合并。
完成以下检查,确认你掌握了 Git 基础:
- 能从头创建一个 Git 仓库
- 能用
add→commit提交代码 - 能用
git diff查看修改了什么 - 会写
.gitignore文件 - 能把代码推送到 GitHub
- 能用
git clone下载别人的项目 - 理解分支的概念,能创建和合并分支
- 遇到合并冲突不慌,知道怎么解决
检查思路与讲解
- 能从零创建仓库、提交、查看 diff、写
.gitignore、push 或 clone,并完成一次分支创建与合并,就算通过。 - 干净的 Git 证据应该包含风险操作前后的
git status --short。 - 发生冲突时,只有两边内容都需要时才保留两边;提交前必须删除所有冲突标记。
- 未提交的错误优先用
git restore,已经共享的历史优先用新提交修复。reset --hard只适合一次性练习仓库。 - 好证据可以是一张分支图、一条短 PR,或展示创建分支、合并、状态干净的终端记录。
学完这一页,至少保留这张证据卡:
- 仓库状态
- 操作前后的 git status
- 操作
- 使用了 init、add、commit、branch、merge、remote、pull 或 push 命令
- 历史
- 显示变更内容的 git log 或分支图
- 失败检查
- 未跟踪文件、错误分支、合并冲突,或远程/认证问题
- 期望产出
- 一份可供其他学习者安全复现的干净 Git 记录