Git 进阶:每周省你几小时的工作流
交互式变基、cherry-pick、bisect、worktree、reflog 救援以及真正好用的分支策略。大多数开发者不知道的 Git 命令,我每天都在用。
大多数开发者学了五个 Git 命令就止步了。add、commit、push、pull、merge。胆子大一点的可能还会加上 checkout 和 branch。这够你撑过第一年。然后你的分支上堆了 47 个提交,消息全是"fix"、"wip"和"求你能跑",你不小心 reset 了不该 reset 的东西,然后在 Stack Overflow 上花了 40 分钟试图撤销一次搞砸的合并。
我用 Git 已经很多年了。不是随便用用——是重度使用。多个分支、多个仓库、多个协作者,整天每天。下面这些是我真正在用的命令和工作流。不是教程里看起来漂亮的那种,而是每周实实在在为我节省时间的那种。
交互式变基:在别人看到之前清理你的烂摊子#
你的分支上有十二个提交。其中一半是"修复拼写错误"。有一个叫"撤销上一次提交"。还有一个叫"这次真的修好了"。你准备开一个 PR。没人需要看到那段历史。
交互式变基就是在分享之前重写你分支上的历史。它可以让你把提交合并在一起、修改消息、重新排序或完全丢弃它们。
基本命令#
git rebase -i HEAD~5这会打开一个编辑器,显示你最近的 5 个提交,从最早的开始:
pick a1b2c3d Add user authentication endpoint
pick d4e5f6g Fix typo in auth middleware
pick h7i8j9k Add rate limiting
pick l0m1n2o Fix rate limit bug
pick p3q4r5s Update auth tests每行开头是一个命令。将 pick 改为以下之一:
squash(或s) ——将此提交合并到上面的提交中,合并消息fixup(或f) ——与 squash 相同,但丢弃此提交的消息reword(或r) ——保留提交但修改消息drop(或d) ——完全删除此提交edit(或e) ——在此提交处暂停变基,以便你修改它
一次真实的清理过程#
下面是我实际的做法。上面那段混乱的历史变成:
pick a1b2c3d Add user authentication endpoint
fixup d4e5f6g Fix typo in auth middleware
pick h7i8j9k Add rate limiting
fixup l0m1n2o Fix rate limit bug
pick p3q4r5s Update auth tests保存关闭。现在你有三个干净的提交而不是五个。拼写修复被折叠到认证提交中,速率限制的 bug 修复被折叠到速率限制提交中。你的 PR 审查者看到的是一个干净、有逻辑的进展。
重新排列提交#
你可以直接重新排列行。如果测试提交应该在速率限制提交之前,就移动那一行:
pick a1b2c3d Add user authentication endpoint
pick p3q4r5s Update auth tests
pick h7i8j9k Add rate limitingGit 会按这个新顺序重放你的提交。如果有冲突,它会暂停让你解决。
autosquash 快捷方式#
如果你知道一个提交是对前一个提交的修复,可以在提交时标记它:
git commit --fixup=a1b2c3d这会创建一个消息为 fixup! Add user authentication endpoint 的提交。然后当你变基时:
git rebase -i --autosquash HEAD~5Git 会自动将 fixup 提交重新排列到它们的目标下方并标记为 fixup。你只需保存关闭即可。不需要手动编辑。
我一直在用这个。这是在保持最终历史干净的同时迭代分支的最快方式。
黄金法则#
永远不要变基已经推送到共享分支的提交。 如果其他人的工作基于那些提交,重写历史会造成真正的问题。在合并前变基你自己的功能分支。永远不要变基 main。
如果你已经推送了功能分支并需要变基:
git push --force-with-lease--force-with-lease 标志比 --force 更安全。如果自你上次 fetch 以来有其他人推送到了同一分支,它会拒绝推送。它不能防止所有问题,但能捕获最常见的一种。
Cherry-Pick:精准的提交迁移#
Cherry-pick 从一个分支取出一个特定的提交并应用到另一个分支。不是合并,不是变基,只是一个提交,干净地应用。
我真正使用这个的场景#
最常见的场景:我在功能分支上修了一个 bug,但这个修复也需要立即应用到 main 或发布分支。我不想合并整个功能分支,只想要那一个修复。
# Find the commit hash of the fix
git log --oneline feature/user-auth
# a1b2c3d Fix null pointer in session validation
# Switch to main and cherry-pick it
git checkout main
git cherry-pick a1b2c3d搞定。修复作为一个新提交出现在 main 上,包含相同的变更。
不提交的 Cherry-Pick#
有时你想应用变更但先不提交。可能你想把几个 cherry-pick 合并为一个提交,或者稍微修改一下变更:
git cherry-pick --no-commit a1b2c3d变更被暂存但没有提交。你可以修改它们,添加更多变更,准备好了再提交。
范围 Cherry-Pick#
需要连续的多个提交?使用范围语法:
git cherry-pick a1b2c3d..f6g7h8i这会 cherry-pick a1b2c3d 之后到 f6g7h8i(含)的所有提交。注意 a1b2c3d 本身是被排除的。如果你想包含它:
git cherry-pick a1b2c3d^..f6g7h8i处理冲突#
Cherry-pick 冲突的工作方式和合并冲突一样。当发生冲突时:
# Git will tell you there's a conflict
# Fix the conflicting files, then:
git add .
git cherry-pick --continue或者如果你改变了主意:
git cherry-pick --abort需要注意的是:cherry-pick 的提交会创建新的提交哈希。如果你之后合并原始分支,Git 通常足够聪明能处理重复。但如果你在最终会合并的分支之间大量 cherry-pick,你可能会看到意外的冲突。手术式使用它,不要把它当作合并策略。
Git Bisect:二分查找 Bug#
有什么东西坏了。你知道两周前还是好的。从那以后有 200 个提交。哪个提交打破了它?
你可以一个一个手动检查。或者你可以用 git bisect,它用二分查找在 log2(n) 步内找到确切的破坏性提交。200 个提交大约是 7-8 次检查而不是 200 次。
手动方式#
# Start bisecting
git bisect start
# Mark the current commit as bad (the bug exists here)
git bisect bad
# Mark a known good commit (the bug didn't exist here)
git bisect good v2.1.0Git 检出 good 和 bad 之间中点的提交。测试它。然后:
# If the bug exists at this commit:
git bisect bad
# If the bug doesn't exist at this commit:
git bisect goodGit 每次将范围缩小一半。经过 7-8 步后,它告诉你:
a1b2c3d4e5f6g7h is the first bad commit
commit a1b2c3d4e5f6g7h
Author: Some Developer <dev@example.com>
Date: Tue Feb 18 14:23:01 2026 +0300
Refactor session handling to use async middleware现在你确切知道是哪个提交引入了 bug。完成后:
git bisect reset这会把你带回开始的地方。
自动化方式(这才是真正的威力)#
如果你有一个测试能检测到 bug,你可以完全自动化整个过程:
git bisect start
git bisect bad HEAD
git bisect good v2.1.0
git bisect run npm test -- --grep "session validation"Git 会自动检出提交、运行测试,并根据退出码标记为 good 或 bad。零表示 good,非零表示 bad。走开,回来后它就告诉你确切的提交。
你可以使用任何脚本:
git bisect run ./test-regression.sh其中 test-regression.sh 是:
#!/bin/bash
npm run build 2>/dev/null || exit 125 # 125 means "skip this commit"
npm test -- --grep "session" || exit 1 # 1 means "bad"
exit 0 # 0 means "good"退出码 125 是特殊的——它告诉 bisect 跳过该提交(当提交无法编译时有用)。这是一个看起来很小众的功能,直到你需要它的那天,然后它帮你省下整个下午。
一次真实的 bisect 过程#
以下是实际操作的样子:
$ git bisect start
$ git bisect bad HEAD
$ git bisect good abc1234
Bisecting: 97 revisions left to test after this (roughly 7 steps)
[def5678...] Commit message here
$ npm test -- --grep "login flow"
# Tests fail
$ git bisect bad
Bisecting: 48 revisions left to test after this (roughly 6 steps)
[ghi9012...] Another commit message
$ npm test -- --grep "login flow"
# Tests pass
$ git bisect good
Bisecting: 24 revisions left to test after this (roughly 5 steps)
...
# After ~7 iterations:
abc1234def5678ghi is the first bad commit7 步就在 200 个提交的大海里找到了那根针。
Git Worktree:多分支,零 stash#
这是最被低估的 Git 功能。我确信大多数开发者不知道它的存在。
问题是:你正在功能分支上深度工作,到处都有修改的文件。然后有人说"你能看一下这个生产 bug 吗?"你有三个选择:
- Stash 所有东西,切换分支,修复,切回来,pop stash。祈祷别出问题。
- 用一个"wip"消息提交你的半成品工作。丑但管用。
- 在另一个目录重新 clone 仓库。
或者选项 4:worktree。
什么是 Worktree#
Worktree 是链接到同一个仓库的第二个(或第三个、第四个)工作目录。每个 worktree 有自己检出的分支、自己的工作文件、自己的暂存区。但它们共享同一个 .git 数据,所以你不是在复制整个仓库。
添加 Worktree#
# You're on feature/user-auth, deep in work
# Need to fix a bug on main:
git worktree add ../hotfix-session main这会创建一个新目录 ../hotfix-session,检出 main。你当前的目录保持原样。没有 stash,没有提交,没有打扰。
cd ../hotfix-session
# Fix the bug
git add .
git commit -m "Fix null pointer in session validation"
git push origin main
cd ../my-project
# Continue working on your feature as if nothing happened在 Worktree 中创建新分支#
git worktree add ../hotfix-nav -b hotfix/nav-crash main这会创建 worktree 并基于 main 创建一个新分支 hotfix/nav-crash。
管理 Worktree#
# List all worktrees
git worktree list
# /home/dev/my-project abc1234 [feature/user-auth]
# /home/dev/hotfix-session def5678 [main]
# Remove a worktree when done
git worktree remove ../hotfix-session
# If the directory was already deleted:
git worktree prune为什么这比 Stash 好#
Stash 对快速上下文切换来说没问题。但对于超过五分钟的事情,worktree 更好:
- 你的 IDE 在功能分支上保持打开。不需要重新索引,不会丢失滚动位置。
- 你可以在两个目录中同时运行测试。
- 没有 stash 冲突或忘记 stash 了什么的风险。
- 你可以在一个 worktree 中运行开发服务器,在另一个中做干净构建。
我通常保持两三个 worktree 活跃:我的主功能分支、一个 main worktree 用于快速检查,有时还有一个 review worktree 用于检出别人的 PR。
唯一的限制#
你不能在两个 worktree 中检出同一个分支。这是设计如此——防止你在两个地方对同一分支做出冲突的修改。如果你尝试,Git 会拒绝。
Reflog:万能撤销按钮#
你做了硬 reset 丢了提交。你删了分支。你变基了然后一切都乱套了。你觉得你的工作没了。
其实没有。Git 几乎从不真正删除任何东西。Reflog 就是你的安全网。
Reflog 是什么#
每次 HEAD 移动——每次提交、检出、变基、reset、合并——Git 都会记录在 reflog 中。它是你的 HEAD 到过的所有地方的日志,按时间顺序。
git reflog输出:
a1b2c3d (HEAD -> main) HEAD@{0}: reset: moving to HEAD~3
f4e5d6c HEAD@{1}: commit: Add payment processing
b7a8c9d HEAD@{2}: commit: Update user dashboard
e0f1g2h HEAD@{3}: commit: Fix auth token refresh
i3j4k5l HEAD@{4}: checkout: moving from feature/payments to main每个条目有一个索引(HEAD@{0}、HEAD@{1} 等)和发生了什么的描述。
硬 Reset 后恢复#
你不小心执行了 git reset --hard HEAD~3 丢了三个提交。它们就在 reflog 里:
# See what you lost
git reflog
# The commit before the reset is HEAD@{1}
git reset --hard f4e5d6c三个提交全回来了。危机解除。
恢复已删除的分支#
你删了一个有未合并工作的分支:
git branch -D feature/experimental
# Oh no, that had two weeks of work提交仍然存在。找到它们:
git reflog | grep "feature/experimental"
# Or just look through the reflog for the last commit on that branch
# Found it. Recreate the branch at that commit:
git branch feature/experimental a1b2c3d分支回来了,所有提交都在。
搞砸变基后恢复#
你变基了然后一切都乱了。到处冲突,错误的提交,一团糟:
# The reflog shows where you were before the rebase
git reflog
# a1b2c3d HEAD@{0}: rebase (finish): ...
# ...
# f4e5d6c HEAD@{5}: rebase (start): checkout main
# b7a8c9d HEAD@{6}: commit: Your last good commit
# Go back to before the rebase
git reset --hard b7a8c9d你回到了变基开始之前的确切位置。就好像什么都没发生过。
30 天安全网#
默认情况下,Git 保留 reflog 条目 30 天(可达提交为 90 天)。之后它们可能被垃圾回收。所以你有一个月来意识到你犯了错误。在实践中,这绰绰有余。
你可以检查过期设置:
git config gc.reflogExpire
# default: 90.days.ago (for reachable)
git config gc.reflogExpireUnreachable
# default: 30.days.ago (for unreachable)如果你特别谨慎,可以增加它:
git config --global gc.reflogExpireUnreachable "180.days.ago"一条个人准则#
在任何破坏性操作之前——硬 reset、强制推送、删除分支——我先运行 git log --oneline -10。我在脑子里记下当前的 HEAD。这只需要两秒钟,已经不止一次让我免于不必要的恐慌。
正确地 Stash:不只是 git stash#
大多数人这样使用 stash:
git stash
# do something
git stash pop这能用,但相当于把所有东西扔进一个标着"杂物"的箱子里。当你有三个 stash 时,你完全不知道哪个是哪个。
给 Stash 命名#
git stash push -m "WIP: user auth form validation"现在列出 stash 时:
git stash list
# stash@{0}: On feature/auth: WIP: user auth form validation
# stash@{1}: On main: Quick fix attempt for nav bug
# stash@{2}: On feature/payments: Experiment with Stripe webhooks你能清楚看到每个 stash 包含什么。
包含未跟踪的文件#
默认情况下,git stash 只暂存被跟踪的文件。你还没 add 的新文件会被留下:
# Stash everything, including new files
git stash push --include-untracked -m "WIP: new auth components"
# Or even include ignored files (rarely needed)
git stash push --all -m "Full workspace snapshot"我几乎每次都用 --include-untracked。切换分支时留下新文件会造成混乱。
部分 Stash#
这是大多数人不知道的。你可以 stash 特定文件:
# Stash only specific files
git stash push -m "Just the auth changes" src/auth/ src/middleware.ts或使用 patch 模式来 stash 文件中的特定代码块:
git stash push --patch -m "Partial: only the validation logic"Git 会逐个遍历每个变更并问你是否要 stash。y 表示是,n 表示否,s 把代码块拆分成更小的片段。
Apply 与 Pop#
# Pop: apply and remove from stash list
git stash pop stash@{2}
# Apply: apply but keep in stash list
git stash apply stash@{2}我在不确定 stash 能否干净应用时使用 apply。如果有冲突,stash 会被保留。pop 的话,如果有冲突,stash 实际上也会留在列表中(很多人不知道这一点),但我更喜欢 apply 的明确意图。
查看 Stash 内容#
# See what files changed in a stash
git stash show stash@{0}
# See the full diff
git stash show -p stash@{0}从 Stash 创建分支#
如果你的 stash 变成了更实质性的东西:
git stash branch feature/auth-validation stash@{0}这会从你最初 stash 时所在的提交创建一个新分支,应用 stash,然后删除它。干净利落。
分支策略:哪个真正好用#
有三种主流的分支策略。每种都有它闪光的场景,也有造成痛苦的场景。
Gitflow#
经典方案。main、develop、feature/*、release/*、hotfix/*。2010 年由 Vincent Driessen 创建。
# Feature branch
git checkout -b feature/user-auth develop
# ... work ...
git checkout develop
git merge --no-ff feature/user-auth
# Release branch
git checkout -b release/2.1.0 develop
# ... final fixes ...
git checkout main
git merge --no-ff release/2.1.0
git tag -a v2.1.0 -m "Release 2.1.0"
git checkout develop
git merge --no-ff release/2.1.0
# Hotfix
git checkout -b hotfix/session-fix main
# ... fix ...
git checkout main
git merge --no-ff hotfix/session-fix
git checkout develop
git merge --no-ff hotfix/session-fix适用场景:移动应用、桌面软件,任何有命名版本发布且需要同时支持多个版本的软件。如果你需要同时维护 v2.1 和 v3.0 并打补丁,Gitflow 能处理。
不适用场景:持续部署的 Web 应用。如果你一天部署 5 次生产环境,release 分支和 develop 分支的仪式就是纯粹的负担。大多数采用 Gitflow 的 Web 团队最终会有一个持续处于坏状态的 develop 分支和没人理解的 release 分支。
GitHub Flow#
简单。你有 main。你创建功能分支。你开 PR。你合并到 main。你部署 main。
git checkout -b feature/user-auth main
# ... work ...
git push origin feature/user-auth
# Open PR, get reviewed, merge
# main is always deployable适用场景:中小团队发布 Web 应用。持续部署。如果 main 总是被部署到生产,这就是你需要的全部。这个站点就是这么用的。
不适用场景:当你需要维护多个发布版本,或者在部署前有漫长的 QA 流程。GitHub Flow 假设 main 会快速进入生产。
主干开发#
每个人都直接提交到 main(「主干」)或通过非常短命的分支(不超过一天)。没有长期运行的功能分支。
# Short-lived branch (merged same day)
git checkout -b fix/auth-token main
# ... small, focused change ...
git push origin fix/auth-token
# PR reviewed and merged within hours, not days适用场景:有良好 CI/CD、全面测试套件和功能标志的高效团队。Google、Meta 和大多数大型科技公司使用主干开发。它强制进行小的、增量的变更,消除合并地狱。
不适用场景:没有良好测试覆盖率或 CI 的团队。如果合并到主干意味着部署未测试的代码,你会不断打破生产环境。你还需要功能标志来处理任何超过一天才能完成的事:
# Feature flag in code
if (featureFlags.isEnabled('new-checkout-flow')) {
renderNewCheckout();
} else {
renderLegacyCheckout();
}我的建议#
对于大多数 Web 开发团队:从 GitHub Flow 开始。它简单、好用,不需要 GitHub/GitLab 已提供之外的工具。
如果你的团队超过 15-20 个工程师并且每天部署多次,看看带功能标志的主干开发。功能标志基础设施的投入在减少合并冲突和加快迭代方面物有所值。
如果你在发布有版本号的软件(移动应用、CLI 工具、库):Gitflow 或其简化版本。你确实需要那些 release 分支。
不要因为一篇博客说哪个策略最好就选它。选那个匹配你实际发布方式的。
Git Hooks:自动化你总是忘记的事#
Git hooks 是在 Git 工作流的特定时间点自动运行的脚本。它们是本地的(不会推送到远程),所以你需要一种方式和团队共享。
真正重要的 Hooks#
pre-commit ——每次提交前运行。用于 lint 和格式化:
#!/bin/bash
# .git/hooks/pre-commit
# Run ESLint on staged files only
STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(js|ts|tsx)$')
if [ -n "$STAGED_FILES" ]; then
echo "Running ESLint on staged files..."
npx eslint $STAGED_FILES --quiet
if [ $? -ne 0 ]; then
echo "ESLint failed. Fix errors before committing."
exit 1
fi
fi
# Run Prettier on staged files
if [ -n "$STAGED_FILES" ]; then
echo "Running Prettier..."
npx prettier --check $STAGED_FILES
if [ $? -ne 0 ]; then
echo "Prettier check failed. Run 'npx prettier --write' first."
exit 1
fi
ficommit-msg ——验证提交消息格式。完美适合强制执行约定式提交:
#!/bin/bash
# .git/hooks/commit-msg
COMMIT_MSG=$(cat "$1")
PATTERN="^(feat|fix|docs|style|refactor|test|chore|perf|ci|build|revert)(\(.+\))?: .{1,72}"
if ! echo "$COMMIT_MSG" | grep -qE "$PATTERN"; then
echo "Invalid commit message format."
echo "Expected: type(scope): description"
echo "Example: feat(auth): add session refresh endpoint"
echo ""
echo "Valid types: feat, fix, docs, style, refactor, test, chore, perf, ci, build, revert"
exit 1
fipre-push ——推送前运行。用于测试:
#!/bin/bash
# .git/hooks/pre-push
echo "Running tests before push..."
npm test
if [ $? -ne 0 ]; then
echo "Tests failed. Push aborted."
exit 1
fiHusky 与原生 Hooks#
原生 hooks 存在于 .git/hooks/ 中。问题是:.git 目录不被 Git 跟踪,所以你无法通过仓库共享 hooks。每个人都得手动设置。
Husky 解决了这个问题。它将 hook 配置存储在仓库中,并在 npm install 时自动设置 .git/hooks:
npx husky init这会创建一个 .husky/ 目录。以文件形式添加 hooks:
# .husky/pre-commit
npx lint-staged结合 lint-staged,你可以获得快速、精准的 pre-commit 检查:
{
"lint-staged": {
"*.{js,ts,tsx}": ["eslint --fix", "prettier --write"],
"*.css": ["prettier --write"],
"*.json": ["prettier --write"]
}
}这只对你实际要提交的文件运行 ESLint 和 Prettier。不是整个代码库。快。
何时跳过 Hooks#
有时你需要不运行 hooks 就提交。紧急热修复、自己分支上的半成品提交:
git commit --no-verify -m "WIP: debugging production issue"谨慎使用。如果你发现自己经常跳过 hooks,你的 hooks 可能太慢或太严格了。
省时间的别名#
我的 .gitconfig 经过多年演进。以下是活下来的别名——我每天真正使用的那些,不是我因为看起来聪明而添加的。
美化日志#
默认的 git log 很啰嗦。这给你一个干净、彩色、带图形的视图:
git config --global alias.lg "log --oneline --graph --all --decorate"用法:
git lg
# * a1b2c3d (HEAD -> main) Fix session validation
# | * d4e5f6g (feature/payments) Add Stripe integration
# | * h7i8j9k Update payment models
# |/
# * l0m1n2o Merge PR #42
# * p3q4r5s Add user dashboard我每天跑这个 20 次。这是理解仓库状态的最快方式。
撤销上一次提交#
保留变更,只是撤销提交:
git config --global alias.undo "reset HEAD~1 --mixed"用法:
git undo
# Commit is gone, but all changes are still in your working directory我在提交太早、忘了某个文件或想重新组织变更时使用这个。
取消暂存所有内容#
git config --global alias.unstage "reset HEAD --"用法:
git unstage src/auth/session.ts
# File is unstaged but changes are preserved列出所有别名#
因为你会忘记自己设置了什么:
git config --global alias.aliases "config --get-regexp ^alias\\."更多我使用的别名#
# Show what you're about to commit
git config --global alias.staged "diff --staged"
# Short status
git config --global alias.st "status -sb"
# Amend without changing the message
git config --global alias.amend "commit --amend --no-edit"
# Show the last commit
git config --global alias.last "log -1 HEAD --stat"
# Pull with rebase instead of merge
git config --global alias.up "pull --rebase --autostash"
# Delete branches that have been merged to main
git config --global alias.cleanup "!git branch --merged main | grep -v '^[ *]*main$' | xargs git branch -d"up 别名特别好。pull --rebase 保持你的历史线性,而不是每次 pull 都创建合并提交。--autostash 在你有脏文件时自动 stash 并恢复你的变更。这才是 pull 默认应该有的行为。
cleanup 别名删除已合并到 main 的本地分支。一段时间后,你会积累大量过时的分支。每周运行一次。
我每天使用的命令#
这些不是别名或高级功能。它们只是我经常运行的、很多开发者似乎不知道的命令。
终极日志命令#
git log --oneline --graph --all这显示每个分支、每次合并、你仓库的整个拓扑结构,以紧凑的视图。这是我拉取变更后第一个运行的命令。它回答「这个仓库现在发生了什么?」
查看暂存的变更#
git diff --staged这显示即将被提交的内容。不是工作目录中的变更——而是实际暂存的内容。我在提交前总是运行这个。总是。它能捕获意外包含的文件、调试语句、不该在那里的 console.log。
# See unstaged changes
git diff
# See staged changes
git diff --staged
# See both
git diff HEAD查看特定提交#
git show a1b2c3d显示单个提交的完整 diff。在审查历史、理解一个提交实际改了什么时很有用。
# Show just the files that changed
git show --stat a1b2c3d
# Show a specific file at a specific commit
git show a1b2c3d:src/auth/session.ts最后那个非常有用。你可以查看历史任何时间点的任何文件,而不需要检出那个提交。
带行范围的 Blame#
git blame -L 42,60 src/auth/session.ts显示谁最后修改了第 42-60 行。比 blame 整个文件有用得多,后者通常信息过载。
# Show the commit message too, not just the hash
git blame -L 42,60 --show-name src/auth/session.ts
# Ignore whitespace changes (very useful)
git blame -w -L 42,60 src/auth/session.ts
# Show the commit before the blamed one (dig deeper)
git log --follow -p -- src/auth/session.ts-w 标志很重要。没有它,blame 会把行归于最后格式化文件的人,而那很少是你要找的人。
在所有历史中查找字符串#
# Find when a function was added or removed
git log -S "validateSession" --oneline
# Find when a regex pattern appeared
git log -G "session.*timeout" --oneline-S(「pickaxe」)找到字符串出现次数发生变化的提交。-G 找到 diff 匹配正则的提交。两者对考古式调查都很强大——弄清楚某样东西是什么时候引入或移除的。
显示两个时间点之间的变化#
# What changed between two branches
git diff main..feature/auth
# What changed on this branch since it diverged from main
git diff main...feature/auth
# List just the changed files
git diff main...feature/auth --name-only
# Stat view (files + insertions/deletions)
git diff main...feature/auth --stat两个点和三个点有区别。两个点显示两个分支尖端之间的差异。三个点显示右侧从左侧分叉后的变化。审查功能分支时你通常想要的是三个点。
清理未跟踪的文件#
# See what would be deleted (dry run)
git clean -n
# Delete untracked files
git clean -f
# Delete untracked files and directories
git clean -fd
# Delete untracked and ignored files (nuclear option)
git clean -fdx始终先用 -n 运行。git clean -fdx 会删除你的 node_modules、.env、构建产物——所有 Git 未跟踪的东西。适合真正的全新开始,但具有破坏性。
从另一个分支恢复单个文件#
# Get the main branch version of a file without switching branches
git restore --source main -- src/config/database.ts或者从特定提交:
git restore --source a1b2c3d -- src/config/database.ts这比旧的 git checkout main -- path/to/file 语法更清晰,而且不影响 HEAD。
整合到一起:一个真实的工作流#
以下是使用这些工具的典型一天:
# Morning: check what's happening
git lg
git fetch --all
# Start a feature
git checkout -b feature/session-refresh main
# Work, commit incrementally
git add -p # Stage specific hunks, not entire files
git commit -m "Add token refresh endpoint"
git commit -m "Add refresh token rotation"
git commit -m "Fix: handle expired refresh tokens"
git commit -m "Add integration tests"
# Interrupted: need to fix a production bug
git worktree add ../hotfix main
cd ../hotfix
# ... fix, commit, push, PR merged ...
cd ../my-project
git worktree remove ../hotfix
# Back to feature work
git commit -m "Fix edge case in token validation"
# Ready for PR: clean up history
git rebase -i main
# Squash the fix commits, reword for clarity
# Push and open PR
git push -u origin feature/session-refresh
# Something broke in staging? Find out which commit:
git bisect start
git bisect bad HEAD
git bisect good main
git bisect run npm test
# Oops, I hard-reset the wrong thing
git reflog
git reset --hard HEAD@{2}这些命令每个只需几秒钟。加在一起,它们每周省下好几个小时。不是假设的小时——而是真实的小时,否则我会花在理清 Git 混乱、手动搜索 Bug 或因上下文切换丢失工作上。
Git 是一个奖励深度使用的工具。基础知识够你撑过一天。但这篇文章中的命令才是把「我用 Git」和「Git 真正让我更快」区分开的东西。逐步学习。这周选一个新技巧,用到形成肌肉记忆。然后再选另一个。
你未来的自己,在晚上 11 点盯着生产 Bug 的时候,会感谢你会用 git bisect 的。