Skip to content

필사 모드: Git 精通指南 — 从基础到 rebase、cherry-pick、bisect、worktree

中文
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.

引言

开发者每天都在用 Git,但大多数人始终没能走出 add-commit-push 的循环。一遇到 merge 冲突就慌,对 rebase 心存畏惧,甚至根本不知道 bisect 和 worktree 的存在。

本文从 Git 的内部结构讲起,涵盖实务中必须掌握的高级命令、分支策略、PR 评审文化、Monorepo 管理,直到危机情况的应对方法,用 350 行以上的篇幅总结Git 的一切


1. 理解 Git 的内部结构

想把 Git 用好,就得理解它的内部结构。Git 本质上是一个键值存储(content-addressable filesystem)。

1.1 四种核心对象

Git 的所有数据都以四种对象类型保存。

对象作用说明
blob文件内容文件的快照(不含名字,只有内容)
tree目录引用 blob 和其他 tree 的列表
commit快照tree + 父提交 + 元数据
tag标签打在特定提交上的 annotated tag

每个对象都由 SHA-1 哈希标识。内容相同就会得到相同的哈希,所以 Git 天然地做到了去重。

# 确认对象类型
git cat-file -t HEAD

# 查看提交对象的内容
git cat-file -p HEAD

# 探索 tree 对象
git ls-tree HEAD

1.2 三个区域

Git 中有三个核心区域。

  • Working Directory -- 实际文件所在的目录。是我们编辑的空间。
  • Staging Area (Index) -- 准备纳入下一次提交的变更所在的区域。
  • Repository (.git) -- 保存提交历史与所有对象的空间。
# 三个区域之间的文件流转
# Working -> Staging
git add file.txt

# Staging -> Repository
git commit -m "feat: add file"

# Repository -> Working(还原特定文件)
git checkout HEAD -- file.txt

1.3 HEAD 与 ref

  • HEAD -- 指向当前检出提交的指针。通常指向某个分支。
  • branch -- 指向特定提交的可移动指针。
  • tag -- 指向特定提交的固定指针。
# 确认 HEAD 指向哪里
cat .git/HEAD
# ref: refs/heads/main

# 确认分支指向的提交
cat .git/refs/heads/main

# 制造 Detached HEAD 状态
git checkout HEAD~2

2. 必备命令速查表

把每天都要用的命令按场景整理了一遍。

2.1 基本工作流

# 初始化 / 克隆仓库
git init
git clone https://github.com/user/repo.git

# 确认变更
git status
git diff                    # unstaged 变更
git diff --staged           # staged 变更
git diff HEAD               # 全部变更

# 暂存与提交
git add -p                  # 交互式部分暂存
git commit -m "feat: login" # 提交
git commit --amend          # 修改最后一次提交

# 与远程同步
git fetch origin            # 只取回远程变更
git pull --rebase origin main  # fetch + rebase
git push origin feature/login  # 推送

2.2 分支管理

# 创建分支并切换
git branch feature/auth
git checkout -b feature/auth  # 创建 + 切换
git switch -c feature/auth    # Git 2.23+ 推荐

# 分支列表
git branch -a               # 本地 + 远程
git branch --merged          # 已合并的分支
git branch -d feature/old    # 安全删除
git branch -D feature/old    # 强制删除

# 清理远程分支
git remote prune origin
git fetch -p

2.3 stash -- 临时保存工作

工作到一半却要紧急切到别的分支时,就用 stash。

# 保存当前变更
git stash
git stash push -m "WIP: login form"

# 查看 stash 列表
git stash list

# 恢复
git stash pop               # 取出的同时删除
git stash apply stash@{0}  # 取出但保留

# 只 stash 特定文件
git stash push -m "partial" -- src/auth.ts

# 把 stash 转成分支
git stash branch new-branch stash@{0}

3. 分支策略对比

3.1 Git Flow

最传统的分支模型,由 Vincent Driessen 在 2010 年提出。

结构

  • main -- 生产代码
  • develop -- 下一个版本的开发
  • feature/* -- 功能开发
  • release/* -- 发布准备
  • hotfix/* -- 紧急缺陷修复

适合的团队:发布周期较长的项目(每月 1-2 次),需要同时维护多个版本的场景。

缺点:分支多导致复杂,与 CI/CD 配合不佳。

3.2 GitHub Flow

GitHub 自己实际使用的简单模型。

结构

  • main -- 始终处于可部署状态
  • feature/* -- 从 main 分出,通过 PR 合并

适合的团队:持续部署环境、小规模团队、Web 服务。

规则:main 必须始终可部署,在 feature 分支上作业后通过 PR 合并。

3.3 Trunk-Based Development

Google、Meta 等大型 IT 企业采用的策略。

结构

  • main (trunk) -- 所有开发者直接提交,或使用 short-lived 分支作业
  • 分支寿命:最多 1-2 天

适合的团队:具备 Feature Flag 基础设施的团队、大规模组织、需要快速反馈循环的场景。

3.4 对比小结

项目Git FlowGitHub FlowTrunk-Based
复杂度
部署频率每月 1-2 次每天每天数次
团队规模大规模中小规模任意规模
是否需要 Feature Flag
与 CI/CD 的契合度一般非常好

4. Merge vs Rebase

这是 Git 中最有争议的话题之一。

4.1 Merge -- 保留历史

# 把 feature 分支 merge 进 main
git checkout main
git merge feature/auth

merge 会找到两个分支的共同祖先并执行 3-way merge,生成一个合并提交(merge commit)。历史被原样保留,因此可以追溯"什么时候在哪个分支上做了什么"。

4.2 Rebase -- 干净的历史

# 在 feature 分支上取得 main 的最新变更
git checkout feature/auth
git rebase main

rebase 会把 feature 分支的提交逐个重新应用到 main 的末端。结果是形成线性历史

4.3 Interactive Rebase -- 整理提交的核心

# 整理最近 5 个提交
git rebase -i HEAD~5

在交互式变基的编辑器里可以使用的命令如下。

  • pick -- 保留提交
  • reword -- 修改提交信息
  • edit -- 修改提交内容
  • squash -- 与前一个提交合并(合并信息)
  • fixup -- 与前一个提交合并(丢弃信息)
  • drop -- 删除提交
# 实际用例:提 PR 之前整理提交
# 在编辑器中改成下面这样
# pick abc1234 feat: add login page
# squash def5678 fix: typo in login
# squash ghi9012 style: adjust padding
# -> 3 个提交被合并成 1 个

4.4 什么时候用哪一个

场景推荐
PR 合并Squash Merge 或 Rebase Merge
整理本地提交Interactive Rebase
共享分支Merge(禁止 rebase)
个人 feature 分支用 Rebase 同步 main

黄金法则:已经 push 出去的提交不要 rebase。别人可能正在引用它。


5. 精通高级命令

5.1 cherry-pick -- 只取特定提交

把其他分支上的某一个提交复制到当前分支。

# 取得特定提交
git cherry-pick abc1234

# 一次取多个提交
git cherry-pick abc1234 def5678

# 按范围取(不含 abc,含 def)
git cherry-pick abc1234..def5678

# 不提交,只应用变更
git cherry-pick --no-commit abc1234

使用场景

  • 把 hotfix 分支的缺陷修复也应用到 develop
  • 把提交错分支的内容挪到正确的分支
  • 在发布分支上只挑选特定功能纳入

5.2 bisect -- 用二分查找定位缺陷

在几百个提交中用二分查找定位缺陷是从哪里引入的。

# 开始 bisect
git bisect start

# 把当前(有缺陷)标记为 bad
git bisect bad

# 把当时正常的提交标记为 good
git bisect good v2.0.0

# Git 会检出中间的提交 -> 测试 -> 判定
git bisect good  # 或者 git bisect bad

# 找到问题提交后重置
git bisect reset

也可以自动化。

# 用测试脚本自动 bisect
git bisect start HEAD v2.0.0
git bisect run npm test

这样一来,Git 会自动在每个提交上跑测试,并找出第一个失败的提交。

5.3 worktree -- 同时在多个分支上作业

可以从一个仓库创建多个工作目录,同时在不同分支上作业。

# 创建新的 worktree
git worktree add ../project-hotfix hotfix/critical-bug

# 查看 worktree 列表
git worktree list

# 移除 worktree
git worktree remove ../project-hotfix

# 一边创建新分支一边创建 worktree
git worktree add -b feature/new-ui ../project-ui

使用场景

  • 在 main 上做代码评审的同时,在 feature 上继续开发
  • 紧急 hotfix 与当前工作同时推进
  • 等待 CI 构建的空档里开始别的工作

5.4 reflog -- 失误恢复的最后防线

reflog 是 HEAD 曾经指向过的所有位置的记录。哪怕失手丢掉了提交,也能靠 reflog 找回来。

# 查看 reflog
git reflog

# 误执行 reset --hard 之后的恢复
git reflog
# 找到 HEAD@{2}: commit: important feature 之后
git reset --hard HEAD@{2}

# 恢复已删除的分支
git reflog
git checkout -b recovered-branch HEAD@{5}

# 查看特定时间段内的记录
git reflog --since="2 days ago"

注意:reflog 只存在于本地,且默认 90 天后过期。


6. 优化 .gitconfig

介绍能提升生产力的 Git 配置。

6.1 好用的 alias

[alias]
    # 状态与日志
    st = status -sb
    lg = log --oneline --graph --decorate --all
    last = log -1 HEAD --stat

    # 分支
    co = checkout
    sw = switch
    br = branch -vv
    brd = branch -d

    # 提交
    cm = commit -m
    ca = commit --amend --no-edit
    undo = reset HEAD~1 --mixed

    # diff
    df = diff --stat
    dfc = diff --cached

    # stash
    sl = stash list
    sp = stash pop
    ss = stash push -m

    # 清理
    cleanup = "!git branch --merged | grep -v '\\*\\|main\\|develop' | xargs -n 1 git branch -d"

6.2 core 配置

[core]
    editor = code --wait
    autocrlf = input          # macOS/Linux
    ignorecase = false
    pager = delta              # 使用 delta pager

[pull]
    rebase = true              # pull 时总是 rebase

[push]
    default = current          # 只 push 当前分支
    autoSetupRemote = true     # push 时自动设置 upstream

[init]
    defaultBranch = main

[diff]
    tool = vscode
    colorMoved = default

[merge]
    conflictstyle = diff3      # 3-way 冲突显示
    tool = vscode

[rerere]
    enabled = true             # 记住冲突的解决方式

6.3 delta -- 更好用的 diff 工具

delta 是把 Git diff 输出变得更易读的工具。

# 安装
brew install git-delta

# 添加到 .gitconfig
[core]
    pager = delta

[interactive]
    diffFilter = delta --color-only

[delta]
    navigate = true
    line-numbers = true
    side-by-side = true

7. GitHub PR 评审最佳实践

7.1 好 PR 的条件

  1. 体量:200-400 行以内。超过就拆开。
  2. 单一目的:一个 PR 只做一件事。
  3. 说明:写清楚为什么需要这个变更、是怎么测试的。
  4. Self-Review:提交之前自己先审一遍。

7.2 PR 模板

## 变更内容
- 改了什么、为什么改

## 测试
- [ ] 新增/修改了单元测试
- [ ] 在本地确认过行为
- [ ] 验证过边界情况

## 截图(涉及 UI 变更时)

## 相关 issue
- closes #123

7.3 评审礼仪

评审者

  • 先理解代码的"意图"。比起风格,更该关注逻辑。
  • 用提问的方式给反馈:"这里为什么这么写?"比"这是错的"要好。
  • 区分 nit、suggestion、question 等评论类型。
  • 明确地使用 Approve、Request Changes、Comment。

作者

  • 尊重评审者的时间。把 PR 保持得小一些。
  • 回应每一条评论(至少给个表情反应)。
  • force push 之后要通知评审者。

7.4 CODEOWNERS

# .github/CODEOWNERS

# 整个代码库
* @team-lead

# 前端
/src/components/ @frontend-team
/src/pages/ @frontend-team

# 后端 API
/src/api/ @backend-team

# 基础设施
/terraform/ @devops-team
/k8s/ @devops-team

# 安全敏感文件
/src/auth/ @security-team @team-lead

8. 提交信息规范

8.1 Conventional Commits

type(scope): description

body(可选)

footer(可选)

type 的种类

type说明
feat新功能
fix缺陷修复
docs文档变更
style代码格式化(不改变功能)
refactor重构
perf性能改进
test测试新增/修改
chore构建、配置变更
ciCI 配置变更
# 示例
git commit -m "feat(auth): add Google OAuth login"
git commit -m "fix(api): handle null response from payment gateway"
git commit -m "docs(readme): update installation instructions"

# Breaking Change
git commit -m "feat(api)!: change response format to JSON:API"

8.2 gitmoji

用表情符号在视觉上区分提交类型。

git commit -m ":sparkles: add user profile page"
git commit -m ":bug: fix login redirect loop"
git commit -m ":recycle: refactor database connection pool"
git commit -m ":white_check_mark: add unit tests for auth module"

8.3 用 commitlint 强制执行

# 安装
npm install -D @commitlint/cli @commitlint/config-conventional

# commitlint.config.js
module.exports = {
  extends: ['@commitlint/config-conventional'],
  rules: {
    'type-enum': [2, 'always', [
      'feat', 'fix', 'docs', 'style',
      'refactor', 'perf', 'test', 'chore', 'ci'
    ]],
    'subject-max-length': [2, 'always', 72],
  },
};

9. Monorepo 管理

9.1 git sparse-checkout

在大规模 Monorepo 中只检出需要的目录。

# 启用 sparse-checkout
git sparse-checkout init --cone

# 只指定需要的目录
git sparse-checkout set packages/frontend packages/shared

# 追加目录
git sparse-checkout add packages/backend

# 确认配置
git sparse-checkout list

# 恢复全部
git sparse-checkout disable

9.2 git subtree

把外部仓库作为子目录纳入统一管理。

# 添加外部仓库
git subtree add --prefix=libs/shared-utils \
  https://github.com/org/shared-utils.git main --squash

# 拉取变更
git subtree pull --prefix=libs/shared-utils \
  https://github.com/org/shared-utils.git main --squash

# 推送变更
git subtree push --prefix=libs/shared-utils \
  https://github.com/org/shared-utils.git main

9.3 与 Nx / Turborepo 联动

把 Monorepo 构建工具和 Git 联动起来,就能只构建、只测试发生变更的包。

# Nx:只测试受影响的项目
npx nx affected --target=test --base=main --head=HEAD

# Turborepo:只构建发生变更的包
npx turbo run build --filter=...[HEAD~1]

# 在 GitHub Actions 中检测变更
# CI 中与 main 比较,只处理发生变更的包

10. 危机情况的应对方法

10.1 force push 的恢复

这是有人误做了 force push 时的应对方法。

# 1. 在 reflog 中找到原来的提交
git reflog show origin/main

# 2. 恢复到原来的提交
git push origin HEAD@{1}:main --force

# 3. 通知其他团队成员
# "main 分支已恢复。请执行 git pull --rebase"

预防措施

# 阻止对 main 分支的 force push(GitHub Settings)
# Settings -> Branches -> Branch protection rules
# 启用 "Restrict force pushes"

10.2 移除敏感信息 (BFG Repo-Cleaner)

这是不小心把密码、API 密钥等提交上去时的做法。

# 安装 BFG
brew install bfg

# 从历史中彻底移除特定文件
bfg --delete-files secrets.env

# 替换特定文本
bfg --replace-text passwords.txt

# 清理
git reflog expire --expire=now --all
git gc --prune=now --aggressive

# 强制推送
git push --force

注意事项

  • 执行 BFG 之前务必备份仓库。
  • 必须要求已经 clone 过的团队成员重新做一次 fresh clone。
  • 可能还需要向 GitHub 申请清除缓存。

10.3 大文件管理 (Git LFS)

二进制文件、媒体文件等大文件要用 Git LFS。

# 安装并初始化 Git LFS
brew install git-lfs
git lfs install

# 指定要追踪的文件模式
git lfs track "*.psd"
git lfs track "*.zip"
git lfs track "assets/videos/*"

# .gitattributes 会自动生成 - 需要提交
git add .gitattributes
git commit -m "chore: configure Git LFS tracking"

# 查看 LFS 文件列表
git lfs ls-files

# 把已经提交的大文件迁移到 LFS
git lfs migrate import --include="*.psd" --everything

10.4 撤销错误的 merge

# revert 掉 merge 提交本身
git revert -m 1 MERGE_COMMIT_SHA

# -m 1: 以第一个父提交(main)为基准撤销
# -m 2: 以第二个父提交(feature)为基准撤销

10.5 什么都丢了的时候的最后手段

# 1. 查看 reflog(90 天以内)
git reflog

# 2. 从 dangling 对象中恢复
git fsck --lost-found

# 3. 最坏的情况:从团队成员的本地仓库恢复
# 请团队成员 push,或者用 bundle 拿过来
git bundle create backup.bundle --all

收尾

Git 不只是一个版本管理工具。它定义团队的协作方式、保证代码质量,是部署流水线的根基,属于核心基础设施

本文讲过的内容可以总结如下。

  1. 理解内部结构之后,命令就变得直观易懂了。
  2. 分支策略要按团队的实际情况来选 -- 没有标准答案。
  3. rebase 用在本地,merge 用在共享分支。
  4. cherry-pick、bisect、worktree、reflog 会在实战中大放异彩。
  5. PR 评审文化决定代码质量。
  6. Conventional Commits 把提交历史保持得干净。
  7. 危机情况下,reflog 和 BFG 就是救命绳。

精通 Git 不是一朝一夕的事。但只要每天试一个新命令,一点点理解它的内部结构,不知不觉间你就会成为在任何情况下都不慌不忙的 Git 高手。

현재 단락 (1/305)

开发者每天都在用 Git,但大多数人始终没能走出 add-commit-push 的循环。一遇到 merge 冲突就慌,对 rebase 心存畏惧,甚至根本不知道 bisect 和 worktree ...

작성 글자: 0원문 글자: 9,937작성 단락: 0/305