跳转到内容

1.2.4 分支与协作

Git 分支协作流程图

这一节解释 Git 为什么能支持安全协作。你会理解分支如何让你在不破坏主线代码的情况下尝试新功能,并初步认识 Pull Request 和合并冲突,为以后参与团队项目和开源贡献做准备。

  • 理解分支的概念和使用场景
  • 掌握创建、切换、合并分支的操作
  • 了解 Pull Request 的协作流程
  • 学会解决简单的合并冲突

想象你住在一间公寓里(main 分支 = 你正在住的家)。你想尝试一种新的装修风格,但不确定效果好不好。

你有两个选择:

  1. 直接在家里改——如果改坏了,你就没得住了
  2. 先租一间一模一样的公寓(新分支),在那边尝试——如果好看就搬过来,不好看就退租

分支就是选项 2。你在新分支上随便改,改好了合并回 main,改坏了直接删掉,main 完全不受影响。

你正在做一个 AI 图像分类项目,main 分支上是能正常运行的代码。

现在你想尝试:

  • 把模型从 CNN 换成 Vision Transformer。
  • 验证效果是不是真的更好。
  • 花几天时间修改可能涉及很多文件的内容。

如果直接在 main 上改,半成品代码可能跑不起来,紧急 bug 修复也很难发布,最后发现 ViT 不合适时还可能要回退几十个文件。

如果用分支,你可以在 feature/vit 上慢慢试;突然要修 bug,就切回 main 处理;实验失败时,直接删掉这个分支即可。


Terminal window
# 查看本地分支(当前分支前面有 * 号)
git branch
# 输出:
# * main
# 查看所有分支(包括远程)
git branch -a
Terminal window
# 创建一个新分支
git branch feature/data-augmentation
# 切换到新分支
git checkout feature/data-augmentation
# 或者一步到位:创建并切换(更常用)
git checkout -b feature/data-augmentation

让我们实际操作一下。继续使用之前的 ai-image-classifier 项目:

Terminal window
cd ai-image-classifier
# 确认当前在 main 分支
git branch
# * main
# 创建并切换到新分支:添加数据增强功能
git checkout -b feature/data-augmentation

现在你在新分支上了。开始写代码:

Terminal window
# 创建数据增强模块
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_transforms
train_transform = get_train_transforms()
test_transform = get_test_transforms()
print("数据增强策略已加载")
EOF
# 提交到当前分支
git add .
git commit -m "feat: 添加数据增强模块(随机翻转、旋转、颜色抖动)"

现在查看两个分支的状态:

Terminal window
# 查看当前分支的历史
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-augmentation
ls src/
# augmentation.py model.py train.py utils.py (有了!)

这就是分支的魔力——两条时间线互不影响。


当你在分支上的功能开发完成、测试通过后,就可以把它合并回 main

Terminal window
# 第一步:切回 main 分支
git checkout main
# 第二步:把 feature 分支合并到 main
git merge feature/data-augmentation

输出:

Updating bbb2222..aaa1111
Fast-forward
src/augmentation.py | 25 +++++++++++++++++++++++++
src/train.py | 5 +++++
2 files changed, 30 insertions(+)
create mode 100644 src/augmentation.py

现在 main 分支也有数据增强代码了:

Terminal window
ls src/
# augmentation.py model.py train.py utils.py ✅
Terminal window
# 功能分支已经合并,可以删掉了(保持仓库整洁)
git branch -d feature/data-augmentation
# 查看分支——只剩 main
git branch
# * main

Git 合并冲突解决流程

当两个分支修改了同一个文件的同一个位置,Git 不知道该保留哪个版本,就会产生冲突。

Terminal window
# 从 main 创建两个分支,模拟两个人同时工作
git checkout -b alice/update-model
cat > src/model.py << 'EOF'
import torch
import 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)
EOF
git add . && git commit -m "alice: 增加滤波器数量到 32"
# 切回 main,创建 bob 的分支
git checkout main
git checkout -b bob/update-model
cat > src/model.py << 'EOF'
import torch
import 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)
EOF
git add . && git commit -m "bob: 改用 64 个滤波器和 5x5 卷积核"

现在合并 工程师 A 的修改:

Terminal window
git checkout main
git merge alice/update-model # ✅ 成功,无冲突

再合并 工程师 B 的修改:

Terminal window
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 torch
import 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)

<<<<<<<=======>>>>>>> 标记全部删掉,只留下你想要的代码。然后:

Terminal window
git add src/model.py
git commit -m "merge: 合并 工程师 A 和 工程师 B 的修改,采用 工程师 B 的 64 滤波器方案"

冲突解决了。

Terminal window
# 清理分支
git branch -d alice/update-model
git branch -d bob/update-model

在团队协作中,你通常不会直接往 main 分支合并。而是通过 Pull Request(PR) 让别人先审查你的代码,确认没问题后再合并。

1. 你创建一个 feature 分支,写代码
2. push 到 GitHub
3. 在 GitHub 上创建 Pull Request
4. 同事审查你的代码,提出修改建议
5. 你根据建议修改,push 新的提交
6. 同事点击 "Approve"(通过)
7. 代码被合并到 main 分支
Terminal window
# 1. 创建分支并写代码
git checkout -b feature/add-evaluation
echo "def evaluate(model, dataloader): pass" > src/evaluate.py
git add . && git commit -m "添加模型评估模块"
# 2. 推送分支到 GitHub
git 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 仓库
  • 能用 addcommit 提交代码
  • 能用 git diff 查看修改了什么
  • 会写 .gitignore 文件
  • 能把代码推送到 GitHub
  • 能用 git clone 下载别人的项目
  • 理解分支的概念,能创建和合并分支
  • 遇到合并冲突不慌,知道怎么解决
检查思路与讲解
  1. 能从零创建仓库、提交、查看 diff、写 .gitignore、push 或 clone,并完成一次分支创建与合并,就算通过。
  2. 干净的 Git 证据应该包含风险操作前后的 git status --short
  3. 发生冲突时,只有两边内容都需要时才保留两边;提交前必须删除所有冲突标记。
  4. 未提交的错误优先用 git restore,已经共享的历史优先用新提交修复。reset --hard 只适合一次性练习仓库。
  5. 好证据可以是一张分支图、一条短 PR,或展示创建分支、合并、状态干净的终端记录。

学完这一页,至少保留这张证据卡:

仓库状态
操作前后的 git status
操作
使用了 init、add、commit、branch、merge、remote、pull 或 push 命令
历史
显示变更内容的 git log 或分支图
失败检查
未跟踪文件、错误分支、合并冲突,或远程/认证问题
期望产出
一份可供其他学习者安全复现的干净 Git 记录