跳到主要内容

Git 工作流

问题​

在团队协作开发中,如何选择合适的 Git 工作流?Git Flow、GitHub Flow、Trunk Based Development 各有什么优劣?如何规范提交信息和版本管理?

面试速答版

如何选择合适的 Git 工作流? 按发布频率和团队规模匹配:

  • 发版周期长、需多版本并行(ToB 软件、SDK) → Git Flow。
  • 持续部署、单一生产版本(Web 应用、SaaS) → GitHub Flow。
  • 大团队、CI/CD 极强、追求合并频率(Google、Meta) → Trunk Based Development。
  • 多数前端 Web 项目首选 GitHub Flow,简单够用。

Git Flow、GitHub Flow、Trunk Based 各有什么优劣? 核心差异是分支数量和合并频率:

  • Git Flow:5 类分支(main/develop/feature/release/hotfix),适合严格版本管理,但太重、合并冲突多。
  • GitHub Flow:只有 main + 短期 feature,PR 合并即部署,简单清爽,但要求生产可回滚。
  • Trunk Based:所有人直接提交 main(或极短分支),靠 Feature Flag 控制未完成功能上线,对自动化测试和 Code Review 要求极高。

如何规范提交信息和版本管理? 两套主流约定配合自动化工具:

  • Conventional Commits:feat: xxx / fix: xxx / chore: xxx,提交信息可机器解析。
  • 校验工具:commitlint + husky 在 commit-msg 钩子拦截不规范的提交。
  • 版本管理:遵循 Semver(major.minor.patch),feat 触发 minor、fix 触发 patch、BREAKING CHANGE 触发 major。
  • 自动化:Changesets(Monorepo)或 semantic-release(单仓库)根据提交记录自动 bump 版本、生成 Changelog、发布。

答案​

Git 工作流是团队协作开发的基石。选择合适的工作流能显著提升开发效率、降低冲突概率、保证代码质量。下面从三大主流工作流、提交规范、版本管理和常用命令等维度全面解析。


一、Git Flow 详解​

Git Flow 是 Vincent Driessen 在 2010 年提出的经典分支模型,适合发布周期较长、需要严格版本管理的项目。

分支结构​

五种分支类型​

分支命名规范生命周期来源合入说明
mainmain / master永久--生产代码,每个 commit 对应一个版本
developdevelop永久main-开发主线,集成最新特性
feature/*feature/login临时developdevelop新功能开发
release/*release/1.0.0临时developmain + develop发布准备,只修 bug
hotfix/*hotfix/1.0.1临时mainmain + develop线上紧急修复

完整操作流程​

git-flow-workflow.sh
# 1. 创建 feature 分支
git checkout develop
git checkout -b feature/user-auth

# 开发完成后合并回 develop
git checkout develop
git merge --no-ff feature/user-auth
git branch -d feature/user-auth

# 2. 创建 release 分支
git checkout develop
git checkout -b release/1.0.0

# 在 release 分支上修复 bug、更新版本号
# 完成后合并到 main 和 develop
git checkout main
git merge --no-ff release/1.0.0
git tag -a v1.0.0 -m "Release version 1.0.0"

git checkout develop
git merge --no-ff release/1.0.0
git branch -d release/1.0.0

# 3. 紧急修复 hotfix
git checkout main
git checkout -b hotfix/1.0.1

# 修复完成后合并到 main 和 develop
git checkout main
git merge --no-ff hotfix/1.0.1
git tag -a v1.0.1 -m "Hotfix version 1.0.1"

git checkout develop
git merge --no-ff hotfix/1.0.1
git branch -d hotfix/1.0.1
要点

--no-ff(no fast-forward)参数非常关键,它会创建一个合并提交,保留分支历史信息。面试中经常会问到 fast-forward 和 no-fast-forward 的区别。

注意

Git Flow 的分支模型较为复杂,对于迭代频繁的互联网项目可能过于繁重。Vincent Driessen 本人在 2020 年也补充说明,Git Flow 并非万能方案,持续交付的项目应考虑更轻量的工作流。


二、GitHub Flow​

GitHub Flow 是 GitHub 推崇的极简工作流,核心理念是 main 分支始终可部署,适合持续部署的 Web 应用。

流程图​

核心规则​

  1. main 分支始终是可部署的
  2. 从 main 创建描述性分支(如 fix/header-layout、feat/dark-mode)
  3. 频繁 push 到远程同名分支
  4. 随时发起 Pull Request 进行讨论
  5. Code Review 通过后合并
  6. 合并后立即部署

操作示例​

github-flow.sh
# 1. 从 main 创建功能分支
git checkout -b feat/dark-mode main

# 2. 开发并频繁推送
git add .
git commit -m "feat: add dark mode toggle"
git push origin feat/dark-mode

# 3. 在 GitHub 上发起 PR(也可用 gh CLI)
gh pr create --title "feat: add dark mode toggle" \
--body "## Summary\n- Add dark mode toggle\n- Persist preference in localStorage"

# 4. Review 通过后在 GitHub 上 Squash and Merge
# 5. 删除远程分支
git push origin --delete feat/dark-mode
补充

GitHub Flow 只有一个长期分支 main,所有功能分支从 main 拉出、合并回 main。它依赖 CI/CD 管道保证代码质量,合并即部署。


三、Trunk Based Development​

Trunk Based Development(TBD)是 Google、Meta 等大厂广泛采用的工作流,核心是所有开发者直接向 trunk(主干)提交代码。

流程图​

核心原则​

  • 短生命周期分支:feature 分支不超过 1-2 天
  • 频繁集成:每天至少合并一次到 trunk
  • Feature Flag:通过特性开关控制未完成功能的可见性
  • Release 分支:仅在需要时从 trunk 拉出,只做 bugfix

Feature Flag 示例​

utils/featureFlags.ts
// Feature Flag 管理
interface FeatureFlags {
darkMode: boolean;
newCheckout: boolean;
aiRecommendation: boolean;
}

const defaultFlags: FeatureFlags = {
darkMode: true,
newCheckout: false,
aiRecommendation: false,
};

// 从远程配置中心获取 Feature Flags
async function getFeatureFlags(): Promise<FeatureFlags> {
try {
const response = await fetch('/api/feature-flags');
const remoteFlags: Partial<FeatureFlags> = await response.json();
return { ...defaultFlags, ...remoteFlags };
} catch {
return defaultFlags;
}
}

// 在组件中使用
function CheckoutPage(): JSX.Element {
const flags = useFeatureFlags();

if (flags.newCheckout) {
return <NewCheckoutFlow />;
}
return <LegacyCheckoutFlow />;
}
要点

Trunk Based Development 要求团队具备较高的工程能力:完善的 CI/CD、充分的自动化测试、成熟的 Feature Flag 基础设施。它能极大地减少分支合并冲突,加速交付速度。


四、三种工作流对比​

维度Git FlowGitHub FlowTrunk Based
分支数量5 种分支2 种(main + feature)1 主干 + 短期分支
复杂度高低中
发布方式版本发布持续部署持续部署
分支生命周期长(feature 可能数周)中(通常数天)短(1-2 天)
适用场景传统软件、移动端 AppWeb 应用、SaaS大型团队、微服务
合并冲突较多适中较少
团队规模中小团队中小团队任意规模
CI/CD 依赖低中高
Feature Flag不需要可选必要
代表公司传统企业GitHub、创业公司Google、Meta
如何选择
  • 小团队 + Web 应用 --> GitHub Flow(简单高效)
  • 需要严格版本控制(如 App、SDK) --> Git Flow
  • 大团队 + 持续交付 --> Trunk Based Development
  • 开源项目 --> GitHub Flow(配合 Fork + PR)

五、Conventional Commits 提交规范​

Conventional Commits 是一套结构化的提交信息规范,与 Semantic Versioning 配合使用,可以自动生成 Changelog、自动决定版本号。

提交格式​

<type>[optional scope]: <description>

[optional body]

[optional footer(s)]

type 类型一览​

type说明版本影响示例
feat新功能minorfeat: add dark mode
fixBug 修复patchfix: resolve login error
docs文档更新-docs: update API guide
style格式调整(不影响逻辑)-style: format with prettier
refactor重构(非新功能、非修复)-refactor: extract utils
perf性能优化patchperf: lazy load images
test测试相关-test: add unit tests
chore构建/工具/依赖-chore: update deps
ciCI 配置-ci: add GitHub Actions
revert回滚-revert: revert feat xyz

提交示例​

conventional-commits-examples.sh
# 普通 feat
git commit -m "feat: add user registration form"

# 带 scope
git commit -m "feat(auth): add OAuth2 login"

# 带 body 和 footer(BREAKING CHANGE 会触发 major 版本升级)
git commit -m "feat(api)!: change response format

BREAKING CHANGE: API response now wraps data in { data, meta } structure.
The old flat response format is no longer supported.

Closes #123"

用 commitlint + husky 强制校验​

npm install --save-dev @commitlint/cli @commitlint/config-conventional husky
npx husky init
echo 'npx commitlint --edit "$1"' > .husky/commit-msg
commitlint.config.ts
import type { UserConfig } from '@commitlint/types';

const config: UserConfig = {
extends: ['@commitlint/config-conventional'],
rules: {
// type 必须是指定值之一
'type-enum': [2, 'always', [
'feat', 'fix', 'docs', 'style', 'refactor',
'perf', 'test', 'chore', 'ci', 'revert',
]],
// subject 不超过 72 个字符
'subject-max-length': [2, 'always', 72],
},
};

export default config;
注意

BREAKING CHANGE 写在 footer 中,或者在 type 后加 !(如 feat!:),都会触发主版本号升级。这是面试中的高频考点。


六、Semantic Versioning 版本号​

Semantic Versioning(语义化版本)格式为 MAJOR.MINOR.PATCH:

v2.3.1
│ │ │
│ │ └── PATCH: 向后兼容的 bug 修复
│ └──── MINOR: 向后兼容的新功能
└────── MAJOR: 不兼容的 API 变更

版本号规则​

变更类型版本变化Commit type示例
Bug 修复1.0.0 -> 1.0.1fix修复表单验证
新功能1.0.1 -> 1.1.0feat添加暗色模式
破坏性变更1.1.0 -> 2.0.0feat! / BREAKING CHANGE修改 API 响应结构

预发布版本​

# alpha: 内部测试版
v2.0.0-alpha.1

# beta: 公测版
v2.0.0-beta.1

# rc: 候选发布版
v2.0.0-rc.1

npm 版本范围​

version-range.ts
// package.json 中的版本范围语法
const dependencies = {
// ^ (caret): 兼容 MINOR 和 PATCH 更新
"react": "^18.2.0", // >=18.2.0 <19.0.0

// ~ (tilde): 仅兼容 PATCH 更新
"lodash": "~4.17.21", // >=4.17.21 <4.18.0

// 精确版本
"typescript": "5.3.3", // 只能是 5.3.3

// 范围
"node": ">=18.0.0", // 大于等于 18
};

七、Changesets 版本管理​

Changesets 是一套用于 Monorepo 的版本管理与 Changelog 生成工具,被 Radix UI、Pnpm 等知名项目采用。

基本流程​

操作示例​

npm install --save-dev @changesets/cli
# 1. 初始化
npx changeset init

# 2. 开发完成后,创建 changeset
npx changeset
# 交互式选择:哪些包受影响、版本升级类型、变更描述

# 3. 消费 changesets,更新版本号和 Changelog
npx changeset version

# 4. 发布
npx changeset publish

创建 changeset 后,会在 .changeset/ 目录下生成一个 markdown 文件:

.changeset/happy-dog-123.md
---
"@mylib/core": minor
"@mylib/react": patch
---

Add dark mode support to core theme engine.
React bindings updated to pass through new theme prop.
补充

Changesets 特别适合 Monorepo 场景。每个 PR 附带一个 changeset 文件描述变更,合并后由 CI 统一处理版本更新和发布,避免了手动管理版本号的混乱。


八、Git 常用命令详解​

1. rebase vs merge​

这是面试中最高频的 Git 问题之一。

对比维度git mergegit rebase
原理创建一个合并提交把 commits 「移植」到目标分支末端
提交历史保留完整分支历史(非线性)线性历史,更整洁
冲突处理一次性解决所有冲突逐个 commit 解决冲突
安全性不改写历史,安全改写 commit hash,公共分支慎用
适用场景合并 feature 到 main在 feature 上同步 main 的更新
rebase-vs-merge.sh
# merge 方式:在 main 上合并 feature
git checkout main
git merge feature/login
# 结果:生成一个 merge commit,保留分支拓扑

# rebase 方式:在 feature 上变基到 main
git checkout feature/login
git rebase main
# 结果:feature 的 commits 被重新应用到 main 最新提交之后
# 注意:rebase 会改写 commit hash,不要对已推送的公共分支 rebase

# 交互式 rebase:整理提交历史
git rebase -i HEAD~3
# 可以 squash(合并)、reword(修改信息)、drop(删除)commits
注意

黄金法则:不要对已推送到远程的公共分支执行 rebase。因为 rebase 会改写 commit hash,其他协作者 pull 时会产生大量冲突。只在本地的 feature 分支上 rebase。

2. cherry-pick​

从其他分支「摘取」特定的 commit 应用到当前分支:

cherry-pick.sh
# 摘取单个 commit
git cherry-pick abc1234

# 摘取多个 commit
git cherry-pick abc1234 def5678

# 摘取一个范围(不包含起始 commit)
git cherry-pick abc1234..def5678
# 包含起始 commit 用三个点:abc1234...def5678

# 遇到冲突时
git cherry-pick --continue # 解决冲突后继续
git cherry-pick --abort # 放弃本次 cherry-pick

典型场景:

  • hotfix 修复后,需要把同一个修复应用到 develop 分支
  • 某个 feature 分支中有一个 commit 被其他分支急需

3. stash​

临时保存工作区的修改,切换到其他分支处理事情后再恢复:

git-stash.sh
# 保存当前修改(包括已暂存和未暂存的)
git stash

# 保存时附带描述信息(推荐)
git stash push -m "WIP: user registration form"

# 查看 stash 列表
git stash list

# 恢复最近的 stash(保留 stash 记录)
git stash apply

# 恢复最近的 stash(删除 stash 记录)
git stash pop

# 恢复指定的 stash
git stash apply stash@{2}

# 保存时包含未追踪文件
git stash push -u -m "include untracked files"

# 从 stash 创建分支(避免恢复时冲突)
git stash branch new-branch stash@{0}

4. reset vs revert​

对比维度git resetgit revert
原理移动 HEAD 指针,「回退」历史创建一个新 commit 来「撤销」某次提交
是否改写历史是否
适用范围本地未推送的提交已推送的公共提交
安全性可能丢失提交安全,不影响其他人
reset-vs-revert.sh
# reset --soft: 回退到指定 commit,保留修改在暂存区
git reset --soft HEAD~1

# reset --mixed(默认): 回退到指定 commit,保留修改在工作区
git reset HEAD~1

# reset --hard: 回退到指定 commit,丢弃所有修改(危险!)
git reset --hard HEAD~1

# revert: 创建一个新 commit 撤销指定提交(安全)
git revert abc1234

# revert 一个 merge commit(需指定保留哪个 parent)
git revert -m 1 <merge-commit-hash>
要点

面试核心回答:reset 是「回退历史」,适合本地操作;revert 是「反向提交」,适合撤销已推送的公共提交。团队协作中优先使用 revert,因为它不改写历史。

5. 其他高频命令​

useful-git-commands.sh
# 查看某个文件的修改历史
git log --oneline --follow -- src/App.tsx

# 查看某次提交的具体改动
git show abc1234

# 查看谁修改了某行代码(排查 bug 利器)
git blame src/utils/auth.ts

# 查看两个分支的差异
git diff main..feature/login

# 修改最近一次提交信息
git commit --amend -m "fix: correct typo in login form"

# 只修改最近一次提交的文件(不改 message)
git add forgotten-file.ts
git commit --amend --no-edit

# 找回被 reset --hard 丢失的提交
git reflog
git reset --hard HEAD@{2}

九、PR / MR 最佳实践​

Pull Request(GitHub)/ Merge Request(GitLab)是 Code Review 的核心载体。

PR 规范​

PR 模板示例​

.github/pull_request_template.md
## 变更类型

- [ ] feat: 新功能
- [ ] fix: Bug 修复
- [ ] refactor: 重构
- [ ] docs: 文档更新
- [ ] test: 测试相关
- [ ] chore: 其他

## 变更描述

<!-- 简要描述本次改动的内容和原因 -->

## 影响范围

<!-- 本次变更影响了哪些模块/页面 -->

## 测试方式

- [ ] 单元测试通过
- [ ] E2E 测试通过
- [ ] 手动测试通过

## 截图(如有 UI 变更)

## 相关 Issue

Closes #

CI 检查清单​

.github/workflows/ci.yml
// 以下为 GitHub Actions 配置的 TypeScript 风格伪代码展示
// 实际 CI 配置使用 YAML

interface CIChecks {
// 必须通过的检查项
lint: 'eslint + prettier 代码规范检查';
typeCheck: 'TypeScript 类型检查';
unitTest: '单元测试 + 覆盖率';
build: '构建是否成功';
commitlint: '提交信息规范检查';

// 可选检查项
e2e?: 'Playwright / Cypress E2E 测试';
bundleSize?: 'Bundle 体积变化检查';
lighthouse?: 'Lighthouse 性能跑分';
preview?: '预览部署(Vercel / Netlify)';
}

实际的 GitHub Actions 配置:

.github/workflows/ci.yml (YAML)
# name: CI
# on: [push, pull_request]
# jobs:
# check:
# runs-on: ubuntu-latest
# steps:
# - uses: actions/checkout@v4
# - uses: pnpm/action-setup@v2
# - uses: actions/setup-node@v4
# with:
# node-version: 20
# cache: pnpm
# - run: pnpm install --frozen-lockfile
# - run: pnpm lint
# - run: pnpm type-check
# - run: pnpm test --coverage
# - run: pnpm build

Code Review 要点​

审查维度关注点
正确性逻辑是否正确,边界条件是否处理
可读性命名是否清晰,注释是否充分
性能是否有不必要的循环、渲染
安全性是否有 XSS、注入风险
测试是否有对应的测试用例
一致性是否符合项目编码规范
可维护性是否容易理解和修改
Code Review 建议
  • PR 大小控制在 200-400 行以内,过大的 PR 审查质量会急剧下降
  • 使用 Draft PR 提前展示设计思路,获取早期反馈
  • Review 时关注「为什么这样做」而不仅仅是「做了什么」
  • 善用 GitHub 的 Suggestion 功能,直接在 Review 中提供修改建议

常见面试问题​

Q1: git rebase 和 git merge 有什么区别?什么时候用 rebase,什么时候用 merge?​

答案:

两者都是整合分支的方式,但原理和效果完全不同:

git merge 会创建一个合并提交(merge commit),保留完整的分支拓扑历史:

# merge 前
# main: A---B---C
# feature: \---D---E

# merge 后
# main: A---B---C-------F (merge commit)
# feature: \---D---E-/

git rebase 会把当前分支的 commits 「移植」到目标分支的末端,产生线性历史:

# rebase 前
# main: A---B---C
# feature: \---D---E

# rebase 后(在 feature 上执行 git rebase main)
# main: A---B---C
# feature: \---D'---E' (新的 commit hash)

使用场景对比:

场景推荐方式原因
feature 合并到 mainmerge --no-ff保留 feature 分支历史
在 feature 上同步 main 最新代码rebase保持 feature 分支线性
已推送到远程的公共分支merge不改写历史,安全
本地未推送的私有分支rebase线性历史,整洁
整理本地多个零碎 commitrebase -i(交互式)squash 合并提交
recommended-workflow.sh
# 推荐工作流:在 feature 分支上用 rebase 同步 main
git checkout feature/login
git rebase main # 把 feature 的 commits 移到 main 最新提交之后

# 合并时用 merge --no-ff 保留历史
git checkout main
git merge --no-ff feature/login

核心原则:永远不要对已推送到远程的公共分支执行 rebase,因为 rebase 会改写 commit hash,导致其他协作者出现冲突。


Q2: Conventional Commits 规范有什么好处?BREAKING CHANGE 是什么?​

答案:

Conventional Commits 是一套结构化的提交信息格式规范,核心好处有三个:

  1. 自动生成 Changelog:工具(如 conventional-changelog)可以根据 commit type 自动分类生成版本变更记录
  2. 自动决定版本号:配合 Semantic Versioning,fix 触发 patch、feat 触发 minor、BREAKING CHANGE 触发 major
  3. 提高可读性:团队成员一眼就能看出每个提交的类型和影响范围

BREAKING CHANGE(破坏性变更)指的是不向后兼容的 API 变更。标记方式有两种:

breaking-change-examples.sh
# 方式一:在 type 后加 !
git commit -m "feat(api)!: change response format to envelope pattern"

# 方式二:在 footer 中写 BREAKING CHANGE
git commit -m "feat(api): change response format

BREAKING CHANGE: Response now uses { data, meta, error } envelope.
Old format { result, status } is removed."

两种方式都会触发主版本号升级(如 1.x.x -> 2.0.0)。

自动化工具链示例:

release-config.ts
// semantic-release 配置
const config = {
branches: ['main'],
plugins: [
'@semantic-release/commit-analyzer', // 分析 commit 类型
'@semantic-release/release-notes-generator', // 生成 release notes
'@semantic-release/changelog', // 更新 CHANGELOG.md
'@semantic-release/npm', // 发布到 npm
'@semantic-release/github', // 创建 GitHub Release
'@semantic-release/git', // 提交版本号变更
],
};

export default config;

配合 commitlint + husky,可以在 git commit 时自动校验提交信息是否符合规范,不符合则拒绝提交。


Q3: 你在团队中是如何管理 Git 工作流和 Code Review 的?​

答案:

这是一道开放性题目,建议从以下几个层面组织回答:

1. 分支策略:

根据项目特点选择合适的工作流。Web 项目通常选择 GitHub Flow,从 main 拉出 feature 分支,通过 PR 合并:

# 分支命名规范
feat/user-auth # 新功能
fix/login-redirect # Bug 修复
refactor/api-client # 重构
chore/update-deps # 依赖更新
docs/api-guide # 文档

2. 提交规范:

使用 Conventional Commits + commitlint + husky 强制校验:

提交规范工具链
// 完整工具链
const toolchain = {
commitlint: '校验 commit message 格式',
husky: 'Git hooks 管理,commit-msg 钩子触发 commitlint',
'lint-staged': 'pre-commit 钩子只检查暂存文件',
czg: '交互式 commit 辅助工具(替代 commitizen)',
};

3. PR / Code Review 流程:

  • PR 使用统一模板,包含变更描述、影响范围、测试方式
  • PR 大小控制在 200-400 行,过大则拆分
  • 至少 1 人 Approve 才能合并
  • CI 必须全部通过(lint、type-check、test、build)
  • 合并策略使用 Squash and Merge,保持 main 分支每个 commit 对应一个完整功能

4. 自动化:

自动化配置一览
const automation = {
'pre-commit': 'lint-staged(ESLint + Prettier)',
'commit-msg': 'commitlint 校验提交信息',
'CI/CD': 'GitHub Actions(lint -> test -> build -> deploy)',
'preview': 'Vercel Preview Deployments(每个 PR 自动部署预览)',
'release': 'changesets 或 semantic-release 自动版本管理',
'dependabot': '自动依赖更新 PR',
};

5. 冲突处理:

  • 鼓励频繁 rebase main 到 feature 分支,减少最终合并冲突
  • 大的重构提前通知团队,避免多人修改同一文件
  • 使用 git rerere(reuse recorded resolution)自动解决重复出现的冲突
enable-rerere.sh
# 开启 rerere,Git 会记住你的冲突解决方式
git config --global rerere.enabled true

相关链接​