Git 基础筑基:从原理到团队协作的全栈实战

《Git 基础筑基:从原理到团队协作的全栈实战》

系列说明 :本系列共 30 讲,从 Git 底层原理出发,覆盖日常开发 99% 的场景, 结合 ecs-fddb 集群 (Git Server + Jenkins + Maven)的真实实验环境, 每讲遵循:故障现象 → 根因分析 → 实验复现 → 修复方案 → 避坑清单


开篇词:为什么你要懂 Git 底层?

"Git 不就是 git add + git commit + git push 吗?"

------ 每个初学者都说过的话,每个踩过坑的人都收回过这句话。

一个真实故障

sql 复制代码
时间:2025-03-15 03:20
环境:生产发布前夜
事件:develop 分支被误合并到 master,生产发布失败
影响:全公司加班到早上 8 点
根因:不了解 Git 分支合并原理,误用 `git merge --no-ff` 导致冲突解决错误

学会 Git 底层,你能:

能力 收益
读懂 .git 目录 故障时能手动修复,不依赖 GUI
理解对象模型 知道 git checkout 到底做了什么
掌握分支本质 不再害怕合并冲突
设计工作流 让团队协作顺畅,Code Review 高效

第1讲:Git 是什么?从 .git 目录读懂 Git 底层原理

1.1 一个灵魂拷问

Q:Git 和 SVN 的本质区别是什么?

对比维度 Git (分布式) SVN (集中式)
存储方式 快照 (Snapshot) 差异 (Delta)
网络依赖 离线可用 必须联网
提交位置 先到本地,再 push 直接到远程
历史完整性 每个 clone 都是完整备份 依赖中央服务器
分支成本 指针操作,瞬间完成 复制目录,昂贵

一句话总结 :Git 是内容寻址文件系统 (Content-Addressable Storage) + 版本控制系统的壳


1.2 实验环境准备

bash 复制代码
# 登录 ecs-fddb Git Server (0001节点)
ssh root@190.92.230.185

# 确认 Git 版本
git --version
# git version 2.43.0

# 创建工作目录
mkdir -p /root/git-lab/{lec1,lec2,lec3}
cd /root/git-lab/lec1

截图 1-1:Git 版本确认截图

在 ecs-fddb-0001 上执行 git --version,确认版本 ≥ 2.30


1.3 .git 目录全景图

1.3.1 初始化一个空仓库

bash 复制代码
# 初始化
git init hello-git
cd hello-git
ls -la .git/

输出示例:

yaml 复制代码
total 24
drwxr-xr-x  7 root root  119 Jun  6 10:00 .
drwxr-xr-x  3 root root   32 Jun  6 10:00 ..
-rw-r--r--  1 root root   23 Jun  6 10:00 HEAD          # 当前分支指针
drwxr-xr-x  2 root root   42 Jun  6 10:00 branches/    # (已废弃,保留兼容)
-rw-r--r--  1 root root   92 Jun  6 10:00 config       # 仓库级配置
-rw-r--r--  1 root root   73 Jun  6 10:00 description  # 仅用于 GitWeb
drwxr-xr-x  2 root root 4096 Jun  6 10:00 hooks/      # 钩子脚本
drwxr-xr-x  2 root root   21 Jun  6 10:00 info/        # 仓库元信息
drwxr-xr-x  2 root root   42 Jun  6 10:00 objects/     # 🔑 核心:对象存储
drwxr-xr-x  2 root root   21 Jun  6 10:00 refs/        # 🔑 核心:引用(分支/tag)

1.3.2 .git 目录核心结构图

scss 复制代码
.git/
├── HEAD              → 指向当前分支 (ref: refs/heads/main)
├── config            → 仓库配置 (user.name, remote 等)
├── objects/          → Git 对象数据库 (核心!)
│   ├── pack/        → 打包的二进制对象文件 (.pack + .idx)
│   └── xx/xxxx...  → 松散对象 (前两字符作目录名)
├── refs/             → 引用 (核心!)
│   ├── heads/       → 分支指针
│   ├── tags/        → 标签指针
│   └── remotes/     → 远程分支指针
├── hooks/            → 钩子脚本 (pre-commit, post-receive 等)
├── logs/             → 引用变更日志 (git reflog 数据源)
├── index             → 暂存区 (Staging Area) 的二进制文件
└── COMMIT_EDITMSG   → 上一次 commit 的消息

🔑 关键认知 :Git 的所有数据 都在 objects/ 里, 所有指针 都在 refs/ 里, HEAD 告诉你现在在哪


1.4 Git 对象模型(核心中的核心)

1.4.1 四种 Git 对象

Git 有 4 种对象 ,用 SHA-1 哈希 唯一标识:

scss 复制代码
┌─────────────────────────────────────────────────────┐
│                  Git 对象关系图                       │
│                                                     │
│    Blob (文件内容)                                    │
│      ↑                                              │
│    Tree (目录结构) ←←←←←←←←←←←←←←←               │
│      ↑                              │              │
│    Commit (提交)  ←←←←←←←←←←←←←←  │              │
│      ↑                              │              │
│    Tag (标签,可选)                 │              │
│                                    张                              │
└─────────────────────────────────────────────────────┘
对象类型 作用 类比
Blob 存储文件内容(不含文件名) 文件内容本身
Tree 存储目录结构(文件名 + 权限 + 指向 Blob/Tree 的指针) 文件夹
Commit 存储提交信息(作者、时间、指向 Tree 的指针、父 Commit) 一次快照的"照片"
Tag 给 Commit 起一个人类友好的名字 里程碑标记

1.4.2 动手实验:亲手创建这 4 种对象

bash 复制代码
cd /root/git-lab/lec1/hello-git

# 【实验 1】创建一个 Blob 对象
echo "Hello Git" > hello.txt
git hash-object -w hello.txt
# 输出:3b18e512dba79e4c8300dd08aeb37f8e728b8dad

# 查看 objects 目录,出现了新文件
find .git/objects -type f
# .git/objects/3b/18e512dba79e4c8300dd08aeb37f8e728b8dad

# 【实验 2】查看 Blob 内容
git cat-file -p 3b18e512dba79e4c8300dd08aeb37f8e728b8dad
# 输出:Hello Git

# 【实验 3】创建一个 Tree 对象
git update-index --add --cacheinfo 100644 \
  3b18e512dba79e4c8300dd08aeb37f8e728b8dad hello.txt
git write-tree
# 输出:4b825dc642cb6eb9a060e54bf8999b8c6bb773eb (这是空树的哈希)
# 实际非空树会有不同的哈希

# 【实验 4】创建一个 Commit 对象
echo "first commit" | git commit-tree 4b825dc642cb6eb9a060e54bf8999b8c6bb773eb
# 输出:abc123... (一个新的 Commit 哈希)

# 【实验 5】查看 Commit 内容
git cat-file -p abc123...
# 输出:
# tree 4b825dc642cb6eb9a060e54bf8999b8c6bb773eb
# author Your Name <you@example.com> 1717632000 +0800
# committer Your Name <you@example.com> 1717632000 +0800
#
# first commit

⚡ 核心洞察

  1. git add = 把文件内容写入 objects/ (创建 Blob)
  2. git commit = 把暂存区写入 Tree → 创建 Commit → 移动分支指针
  3. 文件名不在 Blob 里,而在 Tree 对象里!

1.5 HEAD、分支、引用的关系

1.5.1 HEAD 是什么?

bash 复制代码
cat .git/HEAD
# ref: refs/heads/main

# 如果处于 "detached HEAD" 状态
# 输出:abc123... (直接是一个 Commit 哈希)
sql 复制代码
HEAD
  │
  ├── 正常状态:指向一个分支引用
  │     HEAD → refs/heads/main → abc123... (Commit)
  │
  └── Detached HEAD:直接指向一个 Commit
        HEAD → abc123... (Commit)
        ⚠️ 危险!新 commit 会"悬空",切换分支后找不到

1.5.2 分支的本质

分支就是一个指向 Commit 的指针文件!

bash 复制代码
# 查看 main 分支指向哪个 Commit
cat .git/refs/heads/main
# abc123...

# 创建一个新分支 = 写一个文件
echo "abc123..." > .git/refs/heads/feature-x
git branch
# * main
#   feature-x    ← 出现了!

# 切换分支 = 改写 HEAD 文件
echo "ref: refs/heads/feature-x" > .git/HEAD
git symbolic-ref HEAD
# refs/heads/feature-x

💡 深刻理解

  • 创建分支 = O(1) 操作(写一个 41 字节的文件)
  • 切换分支 = 改写 HEAD + 替换工作区文件
  • 这就是为什么 Git 分支比 SVN 快 100 倍!

1.6 实战:用底层命令 "手工" 完成一次提交

不用 git add + git commit,直接用底层命令走一遍完整流程。

bash 复制代码
cd /root/git-lab/lec1
rm -rf manual-git && mkdir manual-git && cd manual-git
git init

# Step 1: 创建 Blob (git hash-object -w)
echo "version 1" > file.txt
BLOB_HASH=$(git hash-object -w file.txt)
echo "Blob 哈希: $BLOB_HASH"

# Step 2: 写入暂存区 (git update-index)
git update-index --add --cacheinfo 100644 $BLOB_HASH file.txt

# Step 3: 创建 Tree (git write-tree)
TREE_HASH=$(git write-tree)
echo "Tree 哈希: $TREE_HASH"

# Step 4: 创建 Commit (git commit-tree)
COMMIT_HASH=$(echo "首次提交,纯手工" | git commit-tree $TREE_HASH)
echo "Commit 哈希: $COMMIT_HASH"

# Step 5: 更新分支指针 (直接写 refs/heads/main)
echo $COMMIT_HASH > .git/refs/heads/main

# Step 6: 更新 HEAD 指向的工作区
git checkout main

# 验证
git log --oneline
# abc123... (首次提交,纯手工)

git status
# 干净的工作区

截图 1-2:手工提交完整输出截图

展示从 git initgit log 的完整过程,证明你理解了 Git 底层


1.7 三大区域(工作区 / 暂存区 / 版本库)

这是 Git 最重要的** mental model**,必须烂熟于心:

scss 复制代码
┌────────────────────────────────────────────────────────────┐
│                      Git 三大区域                          │
│                                                          │
│  ┌─────────────┐    ┌─────────────┐    ┌─────────────┐ │
│  │  工作区      │    │  暂存区      │    │  版本库      │ │
│  │ Working Dir │───▶│ Staging Area│───▶│ Repository  │ │
│  │ (看得见)     │    │ (index 文件)│    │ (objects/)  │ │
│  └─────────────┘    └─────────────┘    └─────────────┘ │
│       │                    │                    │         │
│       │ git checkout       │ git commit         │         │
│       │ (读 index)         │ (写 tree+commit)  │         │
│       ▼                    ▼                    ▼         │
│  替换工作区文件      生成 commit          永久存储           │
│                                                          │
│  常用命令:              常用命令:           常用命令:       │
│  git status             git add              git commit   │
│  git diff               git rm --cached      git log      │
│  git restore            git diff --cached    git push     │
└────────────────────────────────────────────────────────────┘
区域 实质 操作命令
工作区 操作系统文件系统 git status 看变化
暂存区 (index) .git/index 二进制文件 git add 写入,git diff --cached 查看
版本库 .git/objects/ git commit 写入

实验:观察三大区域的变化

bash 复制代码
cd /root/git-lab/lec1/hello-git

# 修改文件,不 add
echo "change 1" >> hello.txt
git status
# Changes not staged for commit: hello.txt (工作区 ≠ 暂存区)

git diff
# 显示工作区 vs 暂存区的差异

# add 到暂存区
git add hello.txt
git status
# Changes to be committed: hello.txt (暂存区 ≠ 版本库)

git diff --cached
# 显示暂存区 vs 最新 commit 的差异

# commit
git commit -m "change 1"
git status
# clean (三个区域一致)

1.8 踩坑记录

🔴 坑1:rm 删除文件后,git status 还显示文件存在

现象

bash 复制代码
rm hello.txt
git status
# Changes not staged: deleted: hello.txt
# 文件还在暂存区!

根因rm 只删了工作区,没删暂存区。

修复

bash 复制代码
git rm hello.txt    # = rm hello.txt + git add
# 或
git rm --cached hello.txt  # 只删暂存区,保留工作区文件

🟡 坑2:git commit -a 跳过了暂存区,导致提交内容不可控

现象 :以为 git commit -a = git add . + git commit,结果把调试日志也提交了。

根因-a 只自动 add 已被跟踪的文件,新文件还是会漏。

修复

bash 复制代码
# 永远分步操作,review 后再 commit
git add -p    # 交互式选择要提交的 hunk
git diff --cached  # 确认暂存区内容
git commit

🟢 坑3:.gitignore 对已被跟踪的文件无效

现象

bash 复制代码
echo "node_modules/" >> .gitignore
git status
# node_modules/ 还是出现在 untracked 里!

根因.gitignore 只忽略未被跟踪的文件

修复

bash 复制代码
git rm -r --cached node_modules/
echo "node_modules/" >> .gitignore
git add .gitignore
git commit -m "忽略 node_modules"

1.9 本章总结 + 自检清单

学到的核心概念

bash 复制代码
Git = 内容寻址文件系统 + 版本控制壳
       │
       ├── objects/  存储 4 种对象 (blob/tree/commit/tag)
       ├── refs/     存储指针 (分支/tag/远程分支)
       └── HEAD      存储"当前所在位置"

自检清单(全部打 ✓ 才算过关)

  • 能不看资料,画出 Git 四大对象的关系图
  • 能解释 git addgit commit 底层发生了什么
  • 能手工用底层命令完成一次提交
  • 能解释为什么 Git 分支切换如此之快
  • 能说出三大区域的关系,并演示 git diff vs git diff --cached

下节预告

第2讲 :分支管理实战 ------ 从 git branchgit merge / git rebase, 搞懂合并冲突的根本原因,从此不再害怕 CONFLICT (content)




第2讲:分支管理实战 ------ 从 git branch 到合并冲突的本质

2.1 引子:一次真实的合并事故

bash 复制代码
时间:2025-05-20
环境:某互联网金融公司
事件:feature/payment 合并到 develop,冲突解决错误,导致支付金额字段被覆盖
损失:部分订单金额变为 0,全额退款
根因:不了解 Git 合并原理,用 GUI 工具"一键解决冲突",没看 diff

核心问题合并冲突的本质是什么?Git 为什么不能自动解决?


2.2 分支的本质(复习 + 深入)

第1讲说过:分支是一个指向 Commit 的指针文件 。 本节深入:合并的本质是两个分支的 Commit 历史找到"共同祖先",然后三方合并

2.2.1 分支拓扑图(ASCII)

css 复制代码
A ←─ B ←─ C ←─ D  (main)
           │
           └─ E ←─ F  (feature/x)
  • C 是共同祖先 (Merge Base)
  • 合并 feature/xmain = 比较 C↔DC↔F 的差异,合并到新 Commit

2.3 三种合并策略

2.3.1 git merge(合并提交)

bash 复制代码
git checkout main
git merge feature/x

结果 :生成一个新的合并提交 (Merge Commit),有两个父提交。

css 复制代码
A ←─ B ←─ C ←─ D ←─ M  (main)
           │           │
           └─ E ←─ F ──┘
优点 缺点
保留完整历史拓扑 产生大量 Merge Commit,历史图变复杂
不破坏原有 Commit

适用场景 :公共分支合并(如 featuredevelop


2.3.2 git rebase(变基,重写历史)

bash 复制代码
git checkout feature/x
git rebase main

结果 :把 feature/x 的 Commit "摘下来",重新"长"在 main 的最新 Commit 后面。

css 复制代码
# rebase 前
A ←─ B ←─ C ←─ D  (main)
           │
           └─ E ←─ F  (feature/x)

# rebase 后
A ←─ B ←─ C ←─ D  (main)
                    │
                    └─ E' ←─ F'  (feature/x)

⚠️ 黄金法则永远不要 rebase 已经被 push 到远程的公共分支! 会重写历史,让队友的分支基于"已消失的 Commit"。

优点 缺点
历史线性,干净 重写 Commit 哈希,改变历史
避免大量 Merge Commit 操作不当会引发团队灾难

适用场景:本地分支整理 Commit,未 push 前使用


2.3.3 git merge --squash(压缩合并)

bash 复制代码
git checkout main
git merge --squash feature/x
git commit -m "feat: 完成 payment 模块"

结果 :把 feature/x 的所有 Commit 压缩成一个 Commit 再合并。

优点 缺点
历史极其干净,一个功能 = 一个 Commit 丢失细分 Commit 信息
适合 GitHub PR merge squash 策略

2.3.4 三种策略对比表

维度 merge rebase merge --squash
历史完整性 ✅ 完整 ⚠️ 重写 ❌ 压缩
历史整洁度 ❌ 杂乱 ✅ 线性 ✅ 干净
安全性 ✅ 安全 ⚠️ 危险(公共分支) ✅ 安全
适用场景 公共分支合并 本地整理 PR 合并

2.4 合并冲突的本质

2.4.1 什么时候会发生冲突?

ini 复制代码
共同祖先 C 中:line 3 = "hello"
main 修改:line 3 = "hello world"
feature 修改:line 3 = "hello git"
                      ↑ 两边都改了同一行
                      → Git 无法自动合并 → CONFLICT

Git 的合并能力边界

情况 Git 能否自动合并
只有一边改了某行 ✅ 能(recursive 策略自动接受)
两边改了不同文件 ✅ 能
两边改了同一文件的不同区域 ✅ 能(context 足够)
两边改了同一区域的同一行 ❌ 不能 → 冲突

2.4.2 冲突文件的内容格式

c 复制代码
<<<<<<< HEAD
const int MAX_RETRY = 3;    // 当前分支的版本
=======
const int MAX_RETRY = 5;    // 传入分支的版本
>>>>>>> feature/config
  • <<<<<<< HEAD → 当前分支的内容
  • ======= → 分隔线
  • >>>>>>> feature/config → 要合并进来的分支的内容

2.5 实战:模拟并解决一次复杂冲突

实验环境

bash 复制代码
cd /root/git-lab/lec2
git init conflict-demo
cd conflict-demo

# 初始化
echo "line1" > file.txt
echo "line2" >> file.txt
echo "line3" >> file.txt
git add . && git commit -m "初始提交"

Step 1:制造冲突

bash 复制代码
# Terminal 1:main 分支修改
git checkout -b main
sed -i 's/line2/LINE2 by main/' file.txt
git commit -am "main: 修改 line2"

# Terminal 2:feature 分支修改(基于初始提交 fork 出来)
git checkout -b feature conflict-demo
sed -i 's/line2/line2 by feature/' file.txt
git commit -am "feature: 修改 line2"

# 合并,触发冲突
git checkout main
git merge feature
# Auto-merging file.txt
# CONFLICT (content): Merge conflict in file.txt
# Automatic merge failed; fix conflicts and then commit the result.

Step 2:查看冲突状态

bash 复制代码
git status
# On branch main
# You have unmerged paths.
#   (fix conflicts and run "git commit")
#   (use "git merge --abort" to abort the merge)
#
# Unmerged paths:
#   (use "git add <file>..." to mark resolution)
#         both modified:   file.txt

cat file.txt
# line1
# <<<<<<< HEAD
# LINE2 by main
# =======
# line2 by feature
# >>>>>>> feature
# line3

截图 2-1:合并冲突截图

git status + cat file.txt 展示冲突标记

Step 3:解决冲突(正确方式)

bash 复制代码
# 方式1:手动编辑,保留正确内容
cat > file.txt << 'EOF'
line1
LINE2 by main (accepted both changes)
line3
EOF

# 方式2(推荐):用 git checkout --conflict=merge 重新标记后再解决
# 或直接用 mergetool
git mergetool    # 需要配置 meld/vimdiff

# 标记为解决
git add file.txt
git commit -m "merge: 解决 line2 冲突,取 main 的版本"

# 查看合并后的历史
git log --graph --oneline --all

Step 4:用 git show 查看合并 Commit 的差异

bash 复制代码
git show HEAD
# 显示合并提交中,main 和 feature 各自带来的变化

2.6 避免冲突的最佳实践

实践1:小步提交,频繁合并

sql 复制代码
❌ 错误做法:
feature 分支开发 2 周,100+ Commit,一次性合并到 develop
→ 冲突地狱

✅ 正确做法:
每天 rebase develop 到 feature,小步合并
→ 冲突在萌芽状态解决

实践2:用 .gitattributes 指定合并策略

bash 复制代码
# .gitattributes 文件
*.json merge=union          # JSON 文件:两边内容合并,不冲突
*.lock merge=union          # lock 文件:同样合并
*.png binary                # 二进制文件:标记冲突但不尝试合并

实践3:git diff --check 提前发现空白冲突

bash 复制代码
# 提交前检查
git diff --check
# 如果行尾空格、空白行变更,会提前报警

2.7 git rebase 交互模式:整理 Commit 历史

bash 复制代码
# 整理最近 3 个 Commit
git rebase -i HEAD~3

可用的命令

命令 作用
pick 保留 Commit
reword 保留,但修改 commit message
edit 保留,但停下来修改内容
squash 合并到前一个 Commit
fixup 合并到前一个 Commit(丢弃 message)
drop 删除该 Commit

示例:把 3 个 Commit 合并成 1 个

yaml 复制代码
pick abc123 feat: 添加登录接口(初版)
squash def456 fix: 修复登录接口bug
squash ghi789 fix: 再次修复边界情况

保存后,Git 会提示你输入新的 commit message。


2.8 踩坑记录

🔴 坑1:git pull 默认行为 = git fetch + git merge,产生多余 Merge Commit

现象

bash 复制代码
git pull origin main
# 自动生成 Merge branch 'main' of ... 的提交

修复

bash 复制代码
# 设置 pull 默认用 rebase
git config --global pull.rebase true

# 或每次显式指定
git pull --rebase origin main

🟡 坑2:git rebase 过程中遇到冲突,不知道怎么继续

现象

bash 复制代码
git rebase main
# CONFLICT...
# 解决后... 然后呢?

修复

bash 复制代码
# 解决冲突后
git add <resolved-files>
git rebase --continue   # 继续 rebase

# 如果想放弃
git rebase --abort      # 回到 rebase 前的状态

🟢 坑3:用 git commit --amend 修改了已 push 的 Commit

现象 :队友 pull 后报 non-fast-forward,强制 push 才能继续。

根因--amend替换上一个 Commit(生成新的哈希),等于重写历史。

修复

bash 复制代码
# 禁止 amend 已 push 的 commit(除非用 --force-with-lease)
git commit --amend    # ❌ 仅限本地未 push 的 commit

# 如果已经 push 了,用新 commit 修正,不要 amend
git revert <commit>   # 生成一个"反向" commit

2.9 本章总结

核心要点

复制代码
合并冲突的本质 = 同一区域同一行被两边修改,Git 无法自动决策
解决方案 = 理解合并原理 + 小步提交 + 频繁 rebase + 手动解决时看 diff

命令速查表

场景 命令
合并分支(保留历史) git merge <branch>
合并分支(线性历史) git rebase <branch>
压缩合并 git merge --squash <branch>
查看合并冲突 git status + git diff
中止合并 git merge --abort
继续 rebase git rebase --continue
交互式整理 Commit git rebase -i HEAD~N

下节预告

第3讲 :团队协作 Git 工作流 ------ 对比 Git Flow / GitHub Flow / Trunk-Based, 结合 ecs-fddb 集群的 Jenkins 实战,设计一套适合你团队的 Git 工作流。




第3讲:团队协作 Git 工作流 ------ 从混乱到规范

3.1 引子:没有规范的团队协作是什么样?

diff 复制代码
场景:某创业公司,5 个后端开发,没有 Git 工作流规范

日常画风:
- 所有人直接 push 到 main
- feature 分支命名随意:feature1, test, temp, xiaoming
- Commit message: "fix", "改好了", "wip", "asdf"
- 代码审核 = 不存在
- 回滚 = 从同事电脑拷代码

结果:
- main 分支常年处于"不可发布"状态
- 找不到某次 bug 是哪次提交引入的
- 新同事入职 1 周,不敢提交代码

本节目标 :设计一套可落地、可自动化、适合团队规模的 Git 工作流。


3.2 三大主流工作流对比

3.2.1 工作流对比矩阵

维度 Git Flow GitHub Flow Trunk-Based (主线开发)
分支复杂度 🔴 高(5种分支) 🟡 中(2种分支) 🟢 低(1种主线)
适合团队规模 中大型(>10人) 中小型(3-10人) 任何规模(含Monorepo)
发布频率 低(季度/月度) 高(周/天) 极高(每天多次)
CI/CD 集成 复杂 简单 最简单
学习成本
代表用户 Git 官方推荐(早期) GitHub、Netflix Google、Meta、阿里

3.3 工作流详解 + 实战

3.3.1 Git Flow(经典但过时)

bash 复制代码
main (生产)
  ↑
release/v1.2
  ↑
develop (集成)
  ↑
feature/login
  ↑
hotfix/critical-bug

分支职责

分支 生命周期 说明
main 永久 生产环境对应代码
develop 永久 集成测试环境
feature/* 临时 新功能开发
release/* 临时 版本发布准备
hotfix/* 临时 生产紧急修复

完整流程(结合 ecs-fddb Jenkins):

bash 复制代码
# 1. 开发新功能
git checkout develop
git checkout -b feature/user-login
# ... 开发 ...
git commit -m "feat: 添加用户登录接口"
git push origin feature/user-login

# 2. 创建 PR (Pull Request) → Code Review → 合并到 develop
# Jenkins 触发:develop 分支自动构建 + 部署到 TEST 环境

# 3. 发布版本
git checkout develop
git checkout -b release/v1.2.0
# 修 bug、改版本号
git commit -m "chore: bump version to v1.2.0"
git checkout main
git merge --no-ff release/v1.2.0
git tag v1.2.0
git checkout develop
git merge release/v1.2.0

# 4. 紧急修复
git checkout main
git checkout -b hotfix/fix-payment
# ... 修 bug ...
git commit -m "fix: 修复支付金额计算错误"
git checkout main
git merge --no-ff hotfix/fix-payment
git tag v1.2.1

截图 3-1:Git Flow 完整拓扑图

git log --graph --oneline --all --decorate 展示分支拓扑

Git Flow 的致命问题

🔴 分支过多,合并混乱release 分支的修改要同时 merge 回 maindevelop,容易漏

🔴 不适合 CI/CD:发布周期长,不符合持续交付理念

🔴 Git 官方已不推荐git-flow-cheatsheet 已标注"考虑其他工作流"


3.3.2 GitHub Flow(推荐中小团队)

erlang 复制代码
main (永远可发布)
  ↑
feature/* (PR 合并)

核心规则

markdown 复制代码
1. main 分支永远处于可发布状态
2. 新功能从 main 拉 feature 分支
3. feature 分支 push 后,创 Pr (Pull Request)
4. CI 通过 + Code Review 通过 → 合并到 main
5. 合并后自动部署到生产

完整流程(结合 ecs-fddb Jenkins):

bash 复制代码
# 1. 从 main 拉新分支
git checkout main
git pull
git checkout -b feature/add-rate-limit

# 2. 开发,频繁 push
git commit -m "feat: 添加接口限流中间件"
git push origin feature/add-rate-limit

# 3. 在 GitLab/GitHub 创建 PR
# Jenkins 自动触发:
#   - 构建 (mvn clean package)
#   - 单元测试 (mvn test)
#   - 代码扫描 (SonarQube)
#   - 部署到 TEST 环境

# 4. Code Review 通过后,点击 "Merge PR"
# Jenkins 触发:
#   - 合并到 main
#   - 部署到 PROD 环境

# 5. 删除 feature 分支(PR 合并后自动删除)

GitHub Flow 的优势

优势 说明
简单 只有 main + feature/* 两种分支
CI/CD 友好 每次 PR 触发完整流水线
适合持续交付 main 永远可发布,随时可以上线

3.3.3 Trunk-Based Development(Google/Meta 方案)

css 复制代码
main (所有人直接提交)
  ↑
  ├──  Feature Flag (功能开关) 控制未完工功能
  └── 短生命周期分支(<1天)做 Code Review

核心思想所有人向 main 直接提交,用 Feature Flag 而非分支来控制功能发布

bash 复制代码
# 1. 拉最新代码
git checkout main
git pull

# 2. 创建短分支(仅用于 Code Review,不超过1天)
git checkout -b short-live-fix
# ... 修改 ...
git commit -m "fix: 修复连接池泄漏"
git push origin short-live-fix

# 3. 创建 PR → 快速 Review(<4小时)→ 合并
# 或直接 pair programming,跳过 PR

# 4. 功能未完工?用 Feature Flag 包装
java 复制代码
// Feature Flag 示例
if (featureFlag.isEnabled("new-payment")) {
    newPaymentService.process(order);
} else {
    oldPaymentService.process(order);
}

Trunk-Based 的适用场景

条件 是否满足
有完善的自动化测试 ✅ 必须
有 Feature Flag 基础设施 ✅ 必须
团队纪律性强 ✅ 必须
需要极高发布频率(每天多次) ✅ 适合

3.4 结合 ecs-fddb 集群的实战配置

3.4.1 Jenkins + GitHub Flow 完整流水线

groovy 复制代码
// Jenkinsfile (Declarative Pipeline)
pipeline {
    agent any

    triggers {
        // 监听 Git 仓库的 PR 事件
        pollSCM('* * * * *')
    }

    stages {
        stage('Build') {
            steps {
                sh 'mvn clean package -DskipTests'
            }
        }
        stage('Test') {
            steps {
                sh 'mvn test'
            }
        }
        stage('Code Scan') {
            steps {
                sh 'mvn sonar:sonar'
            }
        }
        stage('Deploy to TEST') {
            when {
                branch 'feature/*'
            }
            steps {
                sh 'scp target/*.jar 159.138.147.243:/app/'
                sh 'ssh 159.138.147.243 systemctl restart app'
            }
        }
        stage('Deploy to PROD') {
            when {
                branch 'main'
            }
            steps {
                input message: '确认发布到生产?', ok: '确认'
                sh 'scp target/*.jar 121.91.170.113:/app/'
                sh 'ssh 121.91.170.113 systemctl restart app'
            }
        }
    }
}

3.4.2 .gitlab-ci.yml 配置(如果用 GitLab CI)

yaml 复制代码
stages:
  - build
  - test
  - deploy

variables:
  MAVEN_OPTS: "-Dmaven.repo.local=.m2/repository"

cache:
  paths:
    - .m2/repository/

build:
  stage: build
  script:
    - mvn clean package -DskipTests
  artifacts:
    paths:
      - target/*.jar

test:
  stage: test
  script:
    - mvn test

deploy_test:
  stage: deploy
  only:
    - feature/*
  script:
    - scp target/*.jar deploy@159.138.147.243:/app/
    - ssh deploy@159.138.147.243 "systemctl restart app"

deploy_prod:
  stage: deploy
  only:
    - main
  when: manual
  script:
    - scp target/*.jar deploy@121.91.170.113:/app/
    - ssh deploy@121.91.170.113 "systemctl restart app"

3.5 Commit Message 规范(Conventional Commits)

好的 Commit Message = 可自动生成 Changelog + 自动版本号 bump + 出问题能快速定位

规范格式

xml 复制代码
<type>(<scope>): <subject>

<body>

<footer>
字段 说明 示例
type 提交类型 feat / fix / docs / chore / refactor
scope 影响范围(可选) auth / payment / config
subject 简短描述(<50字符) 添加用户登录接口
body 详细描述(可选) 为什么修改?改了什么?
footer 关联 Issue(可选) Closes #123 / BREAKING CHANGE: ...

完整示例

bash 复制代码
git commit -m "feat(auth): 添加 JWT 令牌刷新接口

- 添加 /api/auth/refresh 接口
- Access Token 过期后,用 Refresh Token 获取新令牌
- 刷新失败返回 401,前端跳转登录页

Closes #45
BREAKING CHANGE: 旧的 /api/auth/token 接口已废弃,请改用 /api/auth/login"

commitlint 强制规范

bash 复制代码
# 安装
npm install -g @commitlint/cli @commitlint/config-conventional

# .commitlintrc.json
{
  "extends": ["@commitlint/config-conventional"]
}

# Git Hook 集成(用的 husky)
npx husky add .husky/commit-msg 'npx --no -- commitlint --edit $1'

3.6 踩坑记录

🔴 坑1:Jenkins 构建时,git checkout 到错误分支

现象

bash 复制代码
Jenkins 控制台输出:
Checking out Revision abc123 (origin/feature/x)
但构建的是 main 分支的代码

根因 :Jenkins Git 插件默认 checkout 到 detached HEAD 状态,分支名变量 ${BRANCH_NAME} 为空。

修复

groovy 复制代码
// Jenkinsfile 中显式指定
checkout([
    $class: 'GitSCM',
    branches: [[name: '*/${BRANCH_NAME}']],
    extensions: [[$class: 'LocalBranch', localBranch: '${BRANCH_NAME}']],
    userRemoteConfigs: [[url: 'git@git-server:repo.git']]
])

🟡 坑2:.gitignore 写的 *.log,但 application.log 还是被跟踪了

现象

bash 复制代码
git add .
# application.log 还是被 add 了!

根因.gitignore 只对未被跟踪的文件有效 。如果 application.log 之前已经被 git add 过,它会被永久跟踪。

修复

bash 复制代码
git rm --cached application.log
echo "*.log" >> .gitignore
git commit -m "chore: 停止跟踪 log 文件"

🟢 坑3:git clone 时,大仓库下载极慢

现象

erlang 复制代码
Cloning into 'monorepo'...
Receiving objects:  15% (12345/80000), 500.00 MiB | 2.00 MiB/s
预计剩余时间:2 小时...

修复

bash 复制代码
# 方式1:只 clone 最新一次提交(shallow clone)
git clone --depth=1 <repo-url>

# 方式2:只 clone 特定分支
git clone --single-branch --branch main <repo-url>

# 方式3:Git 2.19+,部分 clone(只下载需要的对象)
git clone --filter=blob:none <repo-url>

3.7 本章总结

工作流选择建议

css 复制代码
团队规模 < 10人  →  GitHub Flow(简单,CI/CD 友好)
团队规模 10-50人  →  GitHub Flow + Release 分支
团队规模 > 50人,有 Monorepo  →  Trunk-Based + Feature Flag
传统发布周期(季度/半年) →  Git Flow(但不推荐)

规范检查清单

  • 所有 Commit Message 遵循 Conventional Commits 规范
  • 每次 PR 至少 1 人 Code Review
  • CI 流水线覆盖:构建 + 测试 + 代码扫描
  • main 分支受保护,不能直接 push
  • 所有 feature 分支命名规范:feature/<ticket-id>-<short-desc>


第一篇(第1-3讲)总结

sql 复制代码
第1讲:Git 底层原理
  └─ 核心:Git = 内容寻址文件系统,4 种对象 (blob/tree/commit/tag)

第2讲:分支管理 + 合并冲突
  └─ 核心:合并冲突 = 同一行被两边修改;rebase 不应用于公共分支

第3讲:团队协作工作流
  └─ 核心:选对工作流 + 规范 Commit Message + CI/CD 集成

下一篇预告

第二篇:远程仓库与协作进阶(第4-6讲)

  • 第4讲:远程仓库原理(git remote / git fetch / git pull 深度解析)
  • 第5讲:Fork + Pull Request 协作模式(开源贡献标准流程)
  • 第6讲:Git Hooks 自动化(pre-commit / commit-msg / pre-push 实战)

文档版本:v1.0 | 更新时间:2026-06-06 | 适用 Git 版本:≥ 2.30

相关推荐
JakeJiang5 小时前
Git 必备命令指南:从日常高频到项目开发实战
git
叫我少年1 天前
Windows 中安装 git
git
深海鱼在掘金6 天前
Git 完全指南 —— 第1章:Git 概览与版本控制演进
git
noravinsc7 天前
关于Git Flow
git
蜜獾云7 天前
在Git中配置用户名和密码
git
scx_link7 天前
通过git bash在本地创建分支,并推送到远程仓库中
开发语言·git·bash
南大白7 天前
IntelliJ IDEA 运行时的 JVM 本地内存溢出崩溃
git
码农小旋风7 天前
Claude Code 基础用法大全:对话、分析、修改、测试、Git 和工作流
人工智能·git·chatgpt·claude
南大白7 天前
Git 撤回提交完整方案
git