2011年,我第一次接触Git。

那时候Git还不算太普及,SVN还是主流。我用了Git之后,就被它的分布式架构、强大的分支管理、快速的操作速度征服了。从那以后,我就成了Git的忠实用户,所有项目都用Git管理。

到2016年,我已经用了五年Git了。

最开始,我只会git addgit commitgit push三板斧。后来,随着项目越来越复杂、团队越来越大,我开始学习更多的Git技巧——分支、合并、变基、stash、cherry-pick、reflog、bisect……我发现,Git的功能远比我想象的强大,很多技巧能大大提升工作效率。

今天就来分享一些,我用了五年Git总结出来的、你可能不知道的Git技巧。

一、基础但实用的技巧

1. git stash:暂存当前工作

这是我最常用的Git技巧之一。

场景:你正在开发一个新功能,写了一半,突然线上出bug了,需要切换到master分支修bug。但是当前分支的代码还没写完,不想commit(因为提交一个未完成的功能会污染提交历史),也不想丢弃。

这时候,git stash就派上用场了。它能把当前未提交的修改暂存起来,让工作区回到干净的状态,然后你就可以切换分支了。

# 暂存当前修改
git stash

# 暂存并添加备注
git stash save "开发新功能,写了一半"

# 查看暂存列表
git stash list

# 恢复最近的暂存
git stash pop

# 恢复指定的暂存(不删除暂存记录)
git stash apply stash@{1}

# 删除指定的暂存
git stash drop stash@{1}

# 清空所有暂存
git stash clear

git stash是我每天都在用的命令,非常实用。

2. git cherry-pick:拣选提交

场景:你在feature分支上做了一个提交,这个提交也需要应用到master分支上,但是你不想合并整个feature分支(因为feature分支还有其他未完成的提交)。

这时候,git cherry-pick就能派上用场。它能把指定的提交"拣选"出来,应用到当前分支上。

# 拣选指定提交
git cherry-pick <commit-hash>

# 拣选多个提交
git cherry-pick <commit1> <commit2> <commit3>

# 拣选一个范围的提交
git cherry-pick <start>..<end>

# 拣选但不自动提交(可以修改后再提交)
git cherry-pick -n <commit-hash>

git cherry-pick在多分支并行开发、hotfix等场景下非常实用。

3. git reflog:恢复误删的提交

场景:你不小心git reset --hard回退了提交,或者不小心删除了分支,但是那些提交里有重要的代码,想恢复。

这时候,git reflog就能救你一命。它记录了所有的引用变更历史(包括commit、reset、checkout、merge等),即使提交被删除了,只要还在reflog里,就能恢复。

# 查看引用变更历史
git reflog

# 恢复到指定的提交(从reflog中找到commit-hash)
git reset --hard <commit-hash>

# 或者创建一个新分支指向那个提交
git branch recovery <commit-hash>

git reflog是Git的"后悔药",只要提交过,基本都能找回来。reflog默认保留90天,足够你发现和恢复误操作了。

4. git bisect:二分查找bug

场景:线上出了一个bug,但是不知道是哪个提交引入的。最近有几十个提交,一个个查太慢了。

这时候,git bisect就能派上用场。它用二分查找的方式,快速定位引入bug的提交。

# 开始二分查找
git bisect start

# 标记当前版本是坏的(有bug)
git bisect bad

# 标记某个旧版本是好的(没有bug)
git bisect good <commit-hash>

# Git会自动checkout到中间的版本,你测试后告诉Git是好是坏
git bisect good  # 这个版本没有bug
git bisect bad   # 这个版本有bug

# 重复以上步骤,直到Git找到引入bug的提交

# 查找结束后,回到原来的分支
git bisect reset

git bisect能在log(N)次测试内找到引入bug的提交,大大提升排查效率。

二、进阶技巧

5. 交互式rebase:整理提交历史

git rebase(变基)是Git中非常强大但是也容易出错的功能。交互式rebase(git rebase -i)能让你整理提交历史——合并提交、修改提交信息、删除提交、重新排序提交等。

# 交互式rebase最近5个提交
git rebase -i HEAD~5

执行后,会打开编辑器,列出最近5个提交,每个提交前面有一个命令(pick、reword、edit、squash、fixup、drop等):

  • pick:保留这个提交
  • reword:保留提交,但是修改提交信息
  • edit:保留提交,但是暂停,可以修改提交内容
  • squash:把这个提交合并到上一个提交,保留提交信息
  • fixup:把这个提交合并到上一个提交,丢弃这个提交的信息
  • drop:删除这个提交

交互式rebase非常适合在提交PR之前整理提交历史——把多个小提交合并成一个有意义的提交,修改不清晰的提交信息,删除无用的提交。

但是注意:不要对已经推送到远程的提交做rebase,因为rebase会改变提交历史,可能导致其他人的代码冲突。rebase只适合对本地未推送的提交使用。

6. git blame:追溯代码的修改者

场景:你看到一行代码,想知道这行代码是谁写的、什么时候写的、为什么这么写。

这时候,git blame就能派上用场。它能显示每一行代码的最后修改者、修改时间、提交哈希。

# 查看指定文件的blame信息
git blame <file>

# 查看指定行范围的blame信息
git blame -L 10,20 <file>

# 显示邮箱而不是用户名
git blame -e <file>

git blame在排查问题、追溯代码历史时非常实用。配合git show <commit-hash>,能看到这个提交的完整修改内容。

7. git worktree:多工作目录

场景:你需要同时在两个分支上工作(比如一个分支开发新功能,另一个分支修bug),但是不想切换来切换去,也不想克隆两份代码。

这时候,git worktree就能派上用场。它能让一个仓库同时检出多个分支到不同的目录,共享同一个Git仓库数据。

# 创建一个新的工作目录,检出指定分支
git worktree add ../project-bugfix bugfix-branch

# 查看所有工作目录
git worktree list

# 删除工作目录(先删除目录,再prune)
rm -rf ../project-bugfix
git worktree prune

git worktree比克隆两份代码更省空间、更方便,因为多个工作目录共享同一个Git仓库数据,提交和分支都是同步的。

8. git add -p:暂存部分修改

场景:你修改了一个文件的多个地方,但是有些修改想提交,有些修改不想提交(还没写完,或者是调试代码)。

这时候,git add -p(patch模式)就能派上用场。它会把文件的修改分成多个"块"(hunk),逐个问你是否暂存。

# 暂存指定文件的部分修改
git add -p <file>

# 暂存所有文件的部分修改
git add -p

执行后,Git会逐个显示修改块,让你选择:

  • y:暂存这个块
  • n:不暂存这个块
  • s:把这个块拆分成更小的块
  • e:手动编辑这个块
  • q:退出

git add -p能让你精确控制提交内容,避免把不想提交的修改也提交进去。

三、配置和别名

9. Git别名:简化常用命令

Git的命令有时候很长,比如git log --oneline --graph --decorate --all。你可以配置别名,简化常用命令。

# 配置别名
git config --global alias.st status
git config --global alias.co checkout
git config --global alias.br branch
git config --global alias.ci commit
git config --global alias.lg "log --oneline --graph --decorate --all"
git config --global alias.unstage "reset HEAD --"
git config --global alias.last "log -1 HEAD"

配置后,就可以用git st代替git status,用git lg代替长长的log命令,大大提升效率。

我常用的别名:

  • git st:查看状态
  • git co:切换分支
  • git ci:提交
  • git br:查看分支
  • git lg:图形化查看提交历史
  • git unstage:取消暂存
  • git last:查看最后一次提交

10. 其他实用配置

# 设置默认编辑器
git config --global core.editor "vim"

# 设置提交时的用户名和邮箱
git config --global user.name "Your Name"
git config --global user.email "you@example.com"

# 启用颜色输出
git config --global color.ui auto

# 设置默认分支名(Git 2.28+)
git config --global init.defaultBranch main

# 合并时自动rebase(避免产生多余的merge commit)
git config --global pull.rebase true

# 设置merge工具
git config --global merge.tool vimdiff

这些配置能让Git用起来更顺手、更高效。

四、Git钩子(Hooks)

11. Git hooks:自动化操作

Git钩子是在特定事件发生时自动执行的脚本,比如提交前、提交后、推送前、合并前等。你可以用钩子做一些自动化操作。

常用的钩子:

  • pre-commit:提交前执行,比如代码检查、格式化、跑单元测试
  • commit-msg:提交信息检查,比如检查提交信息格式
  • pre-push:推送前执行,比如跑完整测试
  • post-merge:合并后执行,比如安装依赖
  • post-checkout:切换分支后执行,比如安装依赖

钩子脚本放在.git/hooks/目录下,去掉.sample后缀就能生效。

比如,一个简单的pre-commit钩子,提交前跑PHP语法检查:

#!/bin/sh
# 检查暂存的PHP文件语法
FILES=$(git diff --cached --name-only --diff-filter=ACM | grep '\.php$')
if [ -n "$FILES" ]; then
    for FILE in $FILES; do
        php -l "$FILE"
        if [ $? -ne 0 ]; then
            echo "PHP语法错误: $FILE"
            exit 1
        fi
    done
fi
exit 0

Git钩子能让很多操作自动化,提升代码质量和工作效率。

五、Git工作流

12. 常用的Git工作流

用了五年Git,我用过几种不同的工作流,每种工作流都有适用场景:

Git Flow

  • 有master(生产)、develop(开发)、feature(功能)、release(发布)、hotfix(热修复)五种分支
  • 适合有明确发布周期的中大型项目
  • 流程规范,但是比较复杂

GitHub Flow

  • 只有master分支,功能开发用feature分支,合并到master后自动部署
  • 适合持续部署、快速迭代的项目
  • 简单高效,但是要求测试完善

GitLab Flow

  • 在GitHub Flow基础上增加了环境分支(如pre-production、production)
  • 适合有多个环境的项目

Trunk Based Development

  • 所有人都在master(trunk)上开发,用feature flag控制功能发布
  • 适合高频率集成、小步提交的团队

我们团队目前用的是类似GitHub Flow的工作流——master分支是受保护的,功能开发用feature分支,开发完提PR,代码审查通过后合并到master,自动部署到测试环境,验证通过后手动部署到生产环境。

选择哪种工作流,要根据团队规模、项目类型、发布频率来决定,没有最好的,只有最合适的。

六、踩过的坑

用了五年Git,我也踩过不少坑。

坑1:强制推送覆盖了别人的提交

有一次,我在rebase之后,用git push --force强制推送,覆盖了别人刚推送的提交,导致别人的代码丢失。

教训:不要轻易用--force,要用--force-with-lease(如果远程分支有新的提交,会拒绝推送)。而且,不要对多人协作的分支做rebase和force push。

坑2:merge冲突解决错了

有一次,合并分支的时候有冲突,我没仔细看,直接选了"保留我的版本",结果把别人的代码覆盖了,导致线上出bug。

教训:解决merge冲突的时候,一定要仔细看每一处冲突,理解两边的代码逻辑,再决定保留哪一边或者怎么合并。不要图快,随便选一边。

坑3:提交了敏感信息

有一次,我不小心把数据库密码提交到了Git仓库,而且推送到了远程。虽然马上删除了,但是密码已经泄露了(Git历史里还有记录)。

教训:提交前一定要检查提交内容,不要提交敏感信息(密码、密钥、证书等)。如果不小心提交了,要立即修改密码,并且用git filter-branch或BFG Repo-Cleaner清除Git历史中的敏感信息。

坑4:大文件提交

有一次,我不小心把一个几百MB的日志文件提交了,推送到远程后,仓库变得很大,clone很慢。

教训:不要提交大文件(如日志、压缩包、二进制文件)。用.gitignore忽略不需要版本控制的文件。如果不小心提交了大文件,要用git filter-branch或BFG清除。对于需要版本控制的大文件,可以用Git LFS。

七、写在最后

Git用了五年,这些技巧你可能不知道。

从最基础的stash、cherry-pick、reflog、bisect,到进阶的交互式rebase、blame、worktree、add -p,再到配置别名、Git钩子、工作流,Git的功能非常强大,很多技巧能大大提升工作效率。

但是,Git也是一把双刃剑——用得好,能让版本管理得心应手;用不好,可能会丢失代码、搞乱分支、引发冲突。所以,在使用高级功能(如rebase、force push、reset --hard)之前,一定要理解清楚,最好先备份。

最后,给几个建议:

  1. 多用图形化工具:SourceTree、GitKraken、VS Code的Git插件等图形化工具,能让Git操作更直观,减少出错。
  2. 多查文档:Git的文档非常详细,遇到问题先查git help或官方文档。
  3. 多练习:Git是实践性很强的工具,多用多练,才能熟练掌握。
  4. 团队统一规范:团队要统一Git工作流、提交信息规范、分支命名规范,减少协作成本。

最后,用一句话总结:Git不是万能的,但是不会Git是万万不能的。掌握Git的高级技巧,能让你的版本管理事半功倍。

愿每一个开发者,都能熟练掌握Git,让版本管理不再是痛点。愿你的每一次提交,都是有意义的;愿你的每一次合并,都没有冲突。

Git用了五年,我还在学习。这条路,没有终点。