在日常开发里,Git 是那种“天天用、天天记不住”的工具。搜索引擎里关于“git 命令”“git 使用教程”的热度常年居高不下,其实大家缺的不是命令列表,而是“在什么场景下该用哪条命令、为什么这么用”的判断力。这篇东西不打算做命令大全式罗列,我把自己平时真正高频使用、以及关键时刻能救命的 Git 操作,按工作流重新梳理一遍,每个场景都附带选择和理由。适合刚接触 Git 不久、或者用了几年但主要靠“clone、add、commit、push”四板斧的朋友,照着用即可,也能少踩一些我踩过的坑。
1. 从“背命令”到“按工作流用命令”的思路转变
1.1 为什么很多人觉得 Git 难记
Git 命令数量本身不算夸张,真正让人头大的是同一个动作有多个近似指令,比如撤销修改就有 checkout、reset、revert、restore 四种长相相似的去处,新手很容易记混。我早年也是这样过来的,后来把命令按“本地提交、分支操作、远程同步、撤销回滚、排查救援”几个工作流去理解,记忆负担一下就降下来了。命令本质上是在操作一张有向无环图,你每次提交就是在图上追加一个节点,分支只是指向节点的指针,理解这个底层模型后,checkout 为什么能切分支、reset 为什么能移动指针、merge 和 rebase 为什么结果不同,就都有了直观解释。
1.2 一套适合日常开发的命令使用框架
我建议把命令场景分成五类,日常思考时直接套用:
- 提交类:
status、diff、add、commit、log - 分支类:
branch、switch、checkout -b、merge - 远程类:
remote、fetch、pull、push - 撤销/回滚类:
restore、reset、revert、clean - 救援/排查类:
stash、reflog、bisect、blame
这套分类不是官方定义,但很贴合日常开发节奏。比如你正在写代码,突然要切到另一个分支修 bug,手头改动又不想丢,这时候 stash 就属于救援类;如果你直接 checkout 切换,Git 会拦截你并提示冲突,这是很多人第一次碰到“Git 不让我切分支”时的困惑来源。下文按这个框架展开,每条命令都会说明适用场景、常用参数和我在实践中踩过的注意事项。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 每天都离不开的提交与查看命令:那些容易忽略的细节
2.1 status 和 diff:提交前必须养成的习惯
很多开发者的习惯是写完代码直接 git add . 然后 git commit,等到 push 完才发现把调试日志、临时配置文件也提交上去了。正确姿势是先 git status 看变更清单,再用 git diff 确认改了哪些内容。
这两个命令我几乎每天用几十次。git status 默认输出简短状态,但有个容易被忽略的细节:它会区分“已暂存”“未暂存”“未跟踪”三种状态。git diff 默认只显示未暂存部分的差异,想看已暂存内容需要用 git diff --cached。这个区别看似基础,实际能避免“我明明改了为什么不显示 diff”的困惑。
git diff 有几个实用参数值得记住:
bash复制git diff # 查看工作区与暂存区的差异
git diff --cached # 查看暂存区与上次提交的差异
git diff HEAD # 查看工作区与最新一次提交的所有差异
git diff <branch1> <branch2> # 比较两个分支的差异
git diff --stat # 只看变更文件列表和行数统计
提示:如果输出内容太多,可以按
q退出,或配合--stat只看概览。终端情况下建议加上--color=always管道的场景另说,但人眼查看时默认颜色已经很清楚了。
用 git diff --stat 的场景很典型:比如我接到一个需求,改动了 20 个文件,提交前想快速确认改动范围是否合理。一眼扫过去如果发现自己不该碰的文件也在列表里,立刻就能发现是否有误操作。
2.2 add 的三种用法和交互式暂存
git add 是暂存操作的入口,但用法多样,平时用得最多的是这三种:
bash复制git add <file> # 精确指定文件
git add . # 暂存当前目录所有变更
git add -p # 交互式暂存,逐个代码块确认
add -p 是很多人没意识到的宝藏命令。它会把文件里的改动拆成多个 hunk(代码块),让你逐个决定是否暂存。这个能力在做代码评审或精准提交时非常实用。比如我改了一个文件,里面既有修 bug 的改动,又有顺手做的格式调整,想拆成两个 commit 保持历史清晰,git add -p 就是干这个的。
add -p 交互界面里常用按键:
y:暂存当前代码块n:跳过当前代码块s:把当前代码块拆得更细e:手动编辑当前代码块
我见过有同事因为不会拆 commit,导致一个 PR 里有五六个“fix typo”“debug code”的杂乱提交,评审体验很差。用 git add -p 配合后续的 git commit,能让每次提交都只包含一个逻辑变更,这个习惯在团队协作里价值极大。
2.3 写出有信息量的 commit message
git commit 本身简单,难点在于提交信息的质量。我一直遵循“标题 + 正文”的格式:
bash复制git commit -m "feat: 新增用户积分过期提醒功能" -m "实现细节:每天凌晨扫描积分过期记录,通过消息队列推送提醒,避免数据库压力过大;同时补充了定时任务的监控指标。"
第一条 -m 是标题,第二条 -m 是正文,代码里很多人只用一个 -m,其实多 -m 很实用。标题建议遵循 Conventional Commits 规范,也就是 feat:、fix:、docs:、refactor: 这类前缀,大部分团队都认这套格式,配合自动化工具还能自动生成变更日志。
注意:
git commit -a只能暂存已被跟踪文件的修改,新文件不会被包含。如果你想一把梭,用git add . && git commit,而不是以为-a够用。
这里再补充一个细节:git commit --amend 可以修改最近一次提交的 message,也可以把当前暂存区的改动追加进上一次提交。它非常适合“刚提交完发现漏了一个文件”的场景。但有个致命禁忌——如果这个提交已经 push 到远程且其他人已经拉取,千万不要 --amend,因为这会重写历史,导致团队其他人 pull 时冲突。我在这上面栽过一次,教训是:本地未推送的提交随便改,推上去的就要用新提交覆盖。
2.4 log 的查看技巧:别看成一堆 hash
git log 是查看提交历史的命令,但默认输出在提交多的时候非常难读。推荐几个实用变体:
bash复制git log --oneline --graph --decorate -10 # 图形化显示最近10条
git log --author="name" --since="2024-01-01" --until="2024-06-30"
git log -p <file> # 查看某文件每次提交的具体改动
git log --follow <file> # 跟踪文件重命名历史
--oneline --graph --decorate 这个组合我直接配成了别名(alias),效果是一棵清晰的提交树,能直观看到分支合并点。配合 --all 还能看到所有分支的拓扑结构。这个视图比 git log 默认的列表式输出直观得多,排查合并问题尤其好用。
3. 分支操作与合并策略:多人协作场景下的核心动作
3.1 创建和切换分支,新老命令的对比
早期 Git 里切分支只有 git checkout 一个命令,但 checkout 职责过重(既能切分支又能恢复文件),Git 2.23 之后提供了更语义化的 git switch 和 git restore。现在创建和切换分支,我推荐这么用:
bash复制git switch -c <branch-name> # 创建并切换到新分支
git switch <branch-name> # 切换已有分支
git switch - # 切回上一个分支
switch -c 相当于旧版的 checkout -b。用 switch 的好处是命令意图单一,不会像 checkout 那样引发歧义。我个人工作中已经全面转向 switch,只有个别脚本和文档还在用 checkout -b。
关于分支命名,这里分享一个实用建议:用 type/description 的格式,比如 feat/user-login、fix/payment-timeout、refactor/order-service。为什么要这样命名?因为它让分支列表的排序天然具备可读性,而且后续写 CI 脚本、做自动部署匹配分支名时也更好解析。
3.2 merge 和 rebase 的选择逻辑
合并分支有两种主流方式:git merge 和 git rebase。这是 Git 里争议最多的话题,我的使用原则是:功能分支在合并回主干前用 rebase 让历史线性化;主干合并功能分支时用 merge 保留合并点。
bash复制# 场景一:功能分支同步主干最新代码
git switch feat/user-login
git fetch origin
git rebase origin/main
# 场景二:功能分支合并回主干
git switch main
git merge --no-ff feat/user-login
merge --no-ff 会强制生成一个合并节点,而非快进合并,这样主干历史里能清楚看到“这里合并了一个功能”。有人觉得这样历史不干净,但从追溯角度讲,它能清晰地标记功能整体的引入时间点,对做 release 回溯非常友好。
rebase 的原理是将分支上的提交“摘下来”,在目标分支的最新提交基础上重新依次应用。这会导致一个关键差异:rebase 后提交的 hash 会改变。一旦你 rebase 了已经 push 到远程的分支,下次 push 就必须用 --force-with-lease 强制推送。这里必须强调,--force-with-lease 比 --force 安全很多,它会检查远程分支是否已经被别人更新过,避免覆盖他人的提交。
重要提示:永远不要 rebase 共享分支(如 main、develop)上的提交。共享分支是团队所有人的基准线,你重写它的历史意味着所有人需要重新合并自己的分支,很容易引发连锁冲突。
3.3 冲突解决:顺着三条路径你就能看懂谁改了什么
合并或 rebase 时出现冲突是常态,不要慌。冲突标记长这样:
code复制<<<<<<< HEAD
当前分支的代码
=======
另一个分支的代码
>>>>>>> feat/user-login
解决冲突的方式是手动编辑文件,保留想要的代码,删除 <<<<<<<、=======、>>>>>>> 标记,然后:
bash复制git add <resolved-file>
git merge --continue # 或 git rebase --continue
遇到特别复杂的冲突,用 git mergetool 可以打开图形化合并工具(比如 vimdiff、meld),对比三个版本:当前分支、目标分支、共同祖先。理解“共同祖先”很关键,Git 合并不是简单比较两个分支的差异,而是分别计算两边相对于共同祖先的改动,再叠加到一起。两边都改了同一行就会冲突。
排查冲突时最实用的命令是 git log --merge,它能显示两个分支上涉及冲突文件的最近提交,快速定位谁动了这块代码。我经常配合 git blame <file> 查看每一行最后是谁、在哪个提交里修改的,这样解决冲突时能判断哪边改动是更符合当前需求的。
3.4 分支清理与追踪关系维护
分支长了之后仓库会变得混乱。常用清理命令:
bash复制git branch -d <branch-name> # 删除已合并分支
git branch -D <branch-name> # 强制删除未合并分支(谨慎!)
git remote prune origin # 清理远程已删除的跟踪分支
git branch -vv # 查看本地分支与远程分支的追踪关系
git branch -vv 输出里如果显示 [origin/main: gone],说明远程对应的分支已经删除,本地跟踪分支成了“孤儿”,这时候用 git remote prune origin 就能清理掉这些陈旧状态。日常定期执行清理,能避免 git branch 列表越来越长、人眼无法维护的问题。
-D 强制删除是非常危险的命令,我一般是确认分支已经完全合并或已 push 到远程才用。真要删,先 git log 看一眼是否包含独立提交,防止误删。
4. 远程协作与代码同步:把大部分麻烦挡在上传之前
4.1 理解 fetch、pull、push 的真正差异
远程操作是团队协作的核心。很多人把 git pull 理解为“把远程代码拉下来”,但不够精确。git pull 实际上是 git fetch + git merge 的组合:fetch 只把远程提交下载到本地仓库的远程跟踪分支(比如 origin/main),不会动你当前的工作区;merge 才把远程跟踪分支合并进当前分支。
bash复制git fetch origin # 只下载远程数据,不改变工作区
git pull origin main # 等价于:fetch + merge origin/main
git push origin <branch> # 推送本地分支到远程
理解 fetch 和 pull 的区别很重要,因为 fetch 是只读的、安全的,pull 会改变工作区。日常我更习惯主动 git fetch 后再看 git log origin/main 和本地 main 差了几个提交,判断是否存在分叉,再决定用 rebase 还是 merge 来整合。这种“先看再动”的习惯能避免很多意外合并结果。
4.2 push 之前需要检查的三件事
我给自己定过规矩,push 之前必须确认三件事,这些年靠它避免了大量返工:
- 当前分支是否最新:执行
git fetch,再git status,确认本地分支与远程的领先/落后状态。如果落后了,先整合再推送。 - 提交是否经过自测:
git log --oneline -5查看即将推送的提交,确认没有“临时提交”“调试代码”等内容。 - 是否有不可泄露的文件:
git status检查有没有.env、config.local.php之类的敏感文件被误加入追踪。
第三点最容易出事。很多团队在项目根目录维护 .gitignore,但 .gitignore 只对“未跟踪文件”生效,如果你曾经用 git add -f 强制添加过,或者文件已经进了版本库,再在 .gitignore 里写规则也没用。排查方法是用 git ls-files 看当前仓库实际跟踪的文件列表:
bash复制git ls-files | grep -E '\.(env|local|pem|key)$'
如果发现敏感文件被跟踪了,需要及时处理:git rm --cached <file> 把它从索引移除(保留本地文件),然后提交,并在 .gitignore 中加入对应规则。这里的关键是:--cached 只删索引不删工作区文件,避免影响本地配置。
4.3 远程分支的新建、删除与跟踪
推送本地新分支到远程时,通常需要指定上游分支:
bash复制git push -u origin feat/user-login
-u 参数的作用是建立本地分支与远程分支的追踪关系。建立之后,后续直接 git push、git pull 就不用再带远程名和分支名了。查看追踪关系可以用 git remote show origin,它会列出所有跟踪分支和对应的本地分支状态。
删除远程分支的命令:
bash复制git push origin --delete <branch-name>
这条命令常用于线上 feature 分支合并完成后的清理。注意,删除远程分支不会自动删除本地分支,需要单独用 git branch -d。
4.4 浅克隆和稀疏检出:处理超大仓库的两张牌
遇到超大规模仓库时,直接用 git clone 可能很慢,而且会下载全部历史。此时可以用浅克隆:
bash复制git clone --depth=1 <repo-url> # 只克隆最近一次提交
git clone --filter=blob:limit=100k <repo-url> # 限制大 blob 下载
浅克隆对于只需要最近代码、不关心完整历史的场景非常高效。缺点是无法查看全部历史,某些工具(如 git log --all、git blame 跨越缺失历史)会受限。如果后续需要完整历史,可以执行 git fetch --unshallow 补齐。
还有一种场景是仓库很大但你只需要其中某个子目录,比如一个 monorepo 里你只负责 services/api 模块。用稀疏检出可以避免检出不必要的大文件:
bash复制git clone --no-checkout --filter=blob:none <repo-url>
cd <repo-dir>
git sparse-checkout init --cone
git sparse-checkout set services/api
git checkout main
这样工作区只有 services/api 目录,Git 不会把其他模块的 blob 全部下载下来,克隆速度有质的提升。这个功能是 Git 2.25 之后稳定下来的,遇到大仓库我第一反应就是它。
5. 撤销与回滚:用 reset、revert、restore 精准修复各种误操作
5.1 三个“撤销”命令的分工与选择
Git 的撤销命令容易混淆,我给出一个判断矩阵:
| 场景 | 命令 | 说明 |
|---|---|---|
| 修改未暂存,想丢弃工作区改动 | git restore <file> |
恢复文件到暂存区/HEAD 状态,不影响暂存区 |
| 修改已暂存,想取消暂存 | git restore --staged <file> |
只把文件从暂存区放回工作区,改动还在 |
| 修改已暂存,想同时取消暂存并丢弃改动 | git restore --staged --worktree <file> |
两步合一 |
| 想撤销最近一次提交,保留改动在暂存区 | git reset --soft HEAD~1 |
回到提交前状态 |
| 想撤销最近一次提交,保留改动在工作区 | git reset --mixed HEAD~1(默认) |
回到 add 之前 |
| 想彻底删除最近一次提交及其改动 | git reset --hard HEAD~1 |
危险!改动彻底丢失 |
| 想撤销已经 push 的提交 | git revert <commit-hash> |
生成一个反向提交,不改变历史 |
列完这张表你会发现,restore 是“文件级别”的撤销,reset 是“提交级别”的指针移动,revert 是“提交级别”的安全反向操作。理解了这三者的层次,就不会再拿 reset --hard 去撤销远程代码了。
5.2 reset 的三种模式:soft、mixed、hard 的区别
git reset 本质上是在移动 HEAD 指针。它有三种模式,区别在于“移动指针后,暂存区和工作区的内容怎么处理”:
bash复制git reset --soft HEAD~1 # 指针移动,改动留在暂存区
git reset --mixed HEAD~1 # 指针移动,改动回到工作区(取消暂存)
git reset --hard HEAD~1 # 指针移动,直接丢弃改动
用一个实际案例来理解:你刚提交了一个 commit,发现里面包含两个无关改动,想把它们拆成两个提交。先 git reset --soft HEAD~1,这时改动都在暂存区;再用 git reset <file> 把其中一个文件取消暂存;然后分别 commit,就完成了拆分。如果用了 --hard,改动就真的没了,因此我建议 --hard 只在万不得已时才用,且先确认改动有没有其他备份。
提示:
reset --hard之前可以先用git stash把当前改动暂存起来,万一手滑了还能凭 stash 恢复。养成这个随手 stash 的习惯后,我再也没怕过 reset。
5.3 revert 和 revert 的冲突处理
git revert 是撤销“已经推送的提交”的安全方式,它会生成一个新的反向提交,原提交历史保持不变。这样做的好处是团队其他人 pull 时会按正常合并流程应用这个反向提交,不会面临历史重写导致的 pull 冲突。
revert 一个合并提交时比较特殊,比如你 revert 了某个 feature 分支合并到 main 的节点:
bash复制git revert -m 1 <merge-commit-hash>
-m 1 表示保留 main 分支那条线,也就是去掉 feature 分支的改动。如果不带 -m,Git 会不知道是按哪个父分支方向回滚而报错。这是 revert 的一个很容易踩的坑。
revert 也可能失败,比如反向提交和当前分支的最新改动冲突。这时候 Git 会停下来让你手动解决,解决完:
bash复制git add <resolved-file>
git revert --continue
5.4 清理未跟踪文件:clean 的正确使用姿势
git clean 是清理未跟踪文件的命令,也是事故高发地。因为默认它不会清理被 .gitignore 忽略的文件,需要参数指定:
bash复制git clean -n # 预览将要删除的文件,一定先跑这条
git clean -f # 删除工作区所有未跟踪文件
git clean -fd # 删除未跟踪文件和目录
git clean -fdx # 连被 ignore 的文件都删掉(危险!)
我的建议永远是:先 git clean -n 看预览,确认没有误伤再执行。-fdx 尤其要谨慎,它会把你本地生成的依赖缓存(比如 node_modules、.cache)都清掉,虽然不痛不痒,但重新安装可能要花很长时间。这条命令我一个月难用一次,每次用之前都像拆弹一样先过一遍预览列表。
6. 隐藏工作与历史救援:stash、reflog、bisect 等救命技巧
6.1 stash:切换分支时保住未完成工作的最佳方式
日常开发里最典型的场景:正在 feature 分支写代码,线上突然报紧急 bug,需要立刻切到 main 分支修复。当前代码没写完,不想提交也不想丢。这时候 stash 就是救命工具。
bash复制git stash push -m "WIP: 用户登录功能" # 保存当前改动并附上备注
git stash list # 查看已保存的 stash 列表
git stash apply stash@{0} # 恢复到工作区,不删除 stash 记录
git stash pop # 恢复并删除最近一条 stash 记录
git stash drop stash@{0} # 删除指定 stash
apply 和 pop 的区别值得说明:apply 不会删除 stash,适合你恢复后想继续保留这个快照的情况;pop 会删除最近一条 stash,适合“恢复完就完事”的常规流程。我习惯用 push -m 给 stash 加备注,因为 stash 列表一旦超过三条,光靠 hash 前缀你是记不住哪个对应哪个的。
如果 stash 时有未跟踪的新文件,默认不会被包含,需要加 -u:
bash复制git stash push -u -m "包含新文件的改动"
如果 stash 之后又改了其他代码,然后 apply 时可能有冲突,按普通冲突解决流程处理即可。这个命令我几乎每周都用,对时间碎片化严重的开发者来说特别有价值。
6.2 reflog:误删分支、误 reset 后的最后防线
git reflog 是 Git 内部“操作日志”,记录了你本地所有 HEAD 移动的历史。它会记录你执行过的 reset、checkout、commit、rebase 等操作,因此几乎可以恢复任何一次误操作前的位置。
bash复制git reflog
# 输出示例:
# e5f1a2b (HEAD -> main) HEAD@{0}: commit: 修复订单金额计算bug
# 7c9d3e4 HEAD@{1}: reset: moving to HEAD~1
# b8f2a11 HEAD@{2}: checkout: moving from feat/user-login to main
假设我误执行了 git reset --hard HEAD~5,导致 5 个提交的改动全部消失。这时候用 git reflog 找到 reset 之前的 commit hash(比如 b8f2a11),然后:
bash复制git reset --hard b8f2a11
就能完整恢复到 reset 之前的状态。这个命令是我心中 Git 最伟大的保命设计,几乎没有“彻底删除”这一说,只要你的 reflog 还没过期(默认 90 天),误操作就有很大概率救回来。
注意:
reflog只记录本地操作,如果分支已经被删除并且被 GC 清理,那就真的回不来了。所以遇到删除分支的误操作,第一时间查 reflog,别等。
6.3 bisect:用二分法快速定位引入 bug 的提交
线上出现 bug,需要找出是哪个提交引入的,如果历史有几百个提交,逐个检查不现实。git bisect 用二分搜索帮你在 O(log n) 次操作内定位到问题提交。
基本用法:
bash复制git bisect start
git bisect bad # 标记当前提交是坏的
git bisect good <good-hash> # 标记一个已知正常的历史提交
# Git 会检出一个中间提交,你测试它是好是坏
git bisect good # 如果当前检出的提交是好的
git bisect bad # 如果当前检出的提交是坏的
# 重复标记,最终 Git 会告诉你第一个坏提交
git bisect reset # 结束 bisect,回到最初分支
这个命令在定位回归 bug 时非常有用。我做过一次实践:某次发布后线上偶发超时,测试环境复现率低,用 bisect 每天排除几十个提交,最终定位到是某个 commit 里改了连接池配置导致的。如果手动翻提交历史,可能得花一整天,用 bisect 不到半小时就走完了流程。
为了提高 bisect 自动化程度,可以配合 git bisect run <script>,让脚本自动执行测试并返回退出码(0 表示好,非 0 表示坏),完全自动化定位。不过日常用交互模式就够了,关键步骤是选一个“确定正常”的 good 起点,如果起点本身就有问题,整个二分也就失去了意义。
6.4 blame:每行代码背后都有一个故事
git blame 是追溯代码来源的利器,它会逐行显示文件中每一行最后是谁、哪个提交修改的:
bash复制git blame src/services/order-service.js
输出每行内容都带 commit hash、作者、日期。定位某个诡异逻辑为何存在、想找当时上下文时,这个命令直接有效。blame 的局限在于它只显示“最后一次修改”,如果某行经历过多次修改,想看完整演变历史还得配合 git log -L。
git log -L 的使用方式:
bash复制git log -L <start>,<end>:<file>
比如想看 order-service.js 第 50 到 80 行的修改历史,就执行:
bash复制git log -L 50,80:src/services/order-service.js
这个命令对理解一段逻辑的演进过程特别有帮助,配合 blame 使用,基本能还原出这段代码的所有变更脉络。
7. 配置与别名:把 Git 调教成更适合自己的工具
7.1 基础配置:user、editor、line ending 和 merge 工具
Git 安装后第一件事是配置用户信息。有人会问“为什么 commit 历史里作者名是 Unknown”,多半就是没设置 user.name 和 user.email:
bash复制git config --global user.name "Your Name"
git config --global user.email "your.email@example.com"
注意这里的 email 不一定要和 GitHub/GitLab 注册邮箱一致,但最好保持一致,否则提交不会关联到你的账号,Contributions 图也不会更新。如果项目有特殊要求,可以在项目目录用不带 --global 的配置单独覆盖。
日常建议手工配置的还有默认编辑器、行尾符、大小写敏感:
bash复制git config --global core.editor "code --wait"
git config --global core.autocrlf input # macOS/Linux 推荐;Windows 可设 true
git config --global core.ignorecase false # 避免大小写重命名文件的追踪混乱
行尾符问题在 Windows 与 Linux/macOS 混合协作时特别容易引发“整个文件被标记为已修改”的假象。.gitattributes 相比 core.autocrlf 更可靠,因为它能按路径规则控制不同文件类型,比如:
gitattributes复制* text=auto
*.sh text eol=lf
*.bat text eol=crlf
配置 merge 工具也很有用:
bash复制git config --global merge.tool vimdiff
git config --global diff.tool vimdiff
之后 git mergetool 就会调用 vimdiff 来辅助解决冲突。不喜欢 vim 的话,可以换成 meld、beyond compare 或 VS Code 的 code。
7.2 alias:把高频长命令凝练成短命令
别名(alias)是提升 Git 操作效率最直接的途径。我目前最常用的几个:
bash复制git config --global alias.st status
git config --global alias.co checkout
git config --global alias.br branch
git config --global alias.cm "commit -m"
git config --global alias.lg "log --oneline --graph --decorate --all"
git config --global alias.last "log -1 HEAD --stat"
git config --global alias.unstage "restore --staged"
git config --global alias.gone "remote prune origin"
设置完之后,git st 等于 git status,git lg 等于一行漂亮的提交图,git unstage 等于取消暂存。这个配置是一次性投入长期收益的事。我还见过同事把整个团队常用的复查流程做成别名:
bash复制git config --global alias.review "!git fetch origin && git log --oneline origin/main..HEAD"
! 开头的别名会作为 shell 命令执行,可以做复杂组合。这样团队里每个人 git review 一下就能看到自己相比远程 main 分支多了哪些提交,评审前快速自查,极大提高合并效率。
7.3 换行符、文件权限与 .gitignore 的常见坑
日常操作中几个隐蔽的坑,不处理会反复咬人。
第一个是文件权限。Git 默认会把文件的可执行位变化视为变更,如果你的项目在 Windows 和 Linux 之间切换,偶尔会发现明明没改内容却有一堆文件显示 modified。解决方案:
bash复制git config --global core.fileMode false
这会忽略文件权限变化,只关注内容差异。注意它是全局配置,对于需要严格追踪执行权限的项目,请按仓库单独配置。
第二个是 .gitignore 的匹配规则。很多新手以为写 *.log 就完事,实际中还要注意目录层级。/build 只忽略根目录下的 build,build/ 忽略任意层级的 build 目录,*.min.js 不会忽略 vendor/a.min.js,需要写成 vendor/*.min.js。写完后建议用 git check-ignore -v <file> 检查具体是哪个规则匹配了文件,排查“为什么没被 ignore”的时候这个命令是标准答案。
第三个是 CRLF 问题,这是 Windows 开发者绕不开的痛。
bash复制git config --global core.autocrlf true # Windows 专用推荐
git config --global core.autocrlf input # macOS/Linux
但最稳定的方案还是仓库根目录维护 .gitattributes,确保全团队一致。我在的一个项目曾经因为行尾符混乱,导致一次代码评审里 80% 的 diff 都是换行符变化,代码实际改动只有几行,极其痛苦。从那之后我把 .gitattributes 当仓库基础设施来对待。
7.4 免密配置:在 HTTPS 和 SSH 之间做选择
团队协作中每次 push 都输密码很烦,免密配置通常两种方式。
如果使用 HTTPS 协议,可以用 Git 凭据管理器:
bash复制git config --global credential.helper store # 明文存储,简单但安全性弱
git config --global credential.helper cache # 内存缓存,默认15分钟
更推荐的做法是使用操作系统级的凭据管理器,Windows 上 Git for Windows 自带的 Git Credential Manager,macOS 上有 osxkeychain,Linux 上可以用 libsecret。这样可以避免明文密码落盘,安全性好很多。
如果追求更彻底的免密,建议使用 SSH 方式。生成密钥:
bash复制ssh-keygen -t ed25519 -C "your.email@example.com"
将公钥添加到 GitLab/GitHub 的 SSH keys 配置后,操作就变成:
bash复制git clone git@github.com:user/repo.git
git remote set-url origin git@github.com:user/repo.git
SSH 方式的好处是天然免密、安全级别高、无 token 过期问题。短密钥优先用 ed25519,老项目如果需要兼容旧服务器再考虑 rsa。关于“ssh-agent 未启动导致每次都要输 passphrase”的问题,我习惯用 ssh-add -L 先排查,再执行 ssh-add ~/.ssh/id_ed25519。
8. 一些团队协作中的 Git 规范与个人建议
8.1 常用 Git 工作流模型:Git Flow、GitHub Flow、Trunk-Based
不同团队适合不同工作流,我接触过三类比较主流的模型:
- Git Flow:分支类型多(main、develop、feature、release、hotfix),适合版本发布节奏固定的传统项目。缺点是操作复杂度高,对团队要求也高。
- GitHub Flow:只有 main 和 feature 分支,所有改动通过 Pull Request 合并,适合快速迭代的互联网产品。简洁、适合持续部署。
- Trunk-Based Development:所有开发者直接往主干提交,通过特性开关控制功能上线,适合需要极致部署速度的小团队和高自动化团队。
关键观点:工作流是为团队服务的,不是为了流程本身。小团队用 Git Flow 反而会让人觉得 Git 很重;大项目用 Trunk-Based 但自动化测试跟不上,一样天天翻车。选择的核心指标是“部署频率”和“团队规模/经验”。我建议大多数团队从 GitHub Flow 起步,等版本管理需求明确后再逐步细化。
8.2 提交粒度和代码评审中的 Git 操作习惯
关于提交粒度,我坚持“一个提交只做一件事”。评审一个 feature 分支时,如果看到十几条无意义的“update”“fix”,很难判断每个提交的价值;但如果每个提交都有清晰的语义,比如“feat: 新增接口”“refactor: 抽取公共方法”“fix: 修复空指针”,评审体验会好很多。
具体操作习惯:
- 写代码过程中频繁
git status和git diff,即时知道自己在改什么。 - 需要暂存一部分内容时用
git add -p,精确控制提交内容。 - 提交信息遵循团队规范(不强求 Conventional Commits 但至少要有清晰标题)。
- 提交后发现遗漏,用
git commit --amend补充(仅限未推送的提交)。 - push 前用
git lg看一遍提交拓扑,确认没有走错分支。
代码评审场景里,我常会用到几个命令组合:git fetch 拿到最新远程状态;git diff origin/main...HEAD 看当前分支相对 main 的全部差异;git log --oneline origin/main..HEAD 看本分支要合入哪些提交。这比在 GitHub/GitLab 网页上傻等加载快得多,还可以直接用编辑器打开对比,效率很高。
8.3 我踩过的几个典型 Git 事故与复盘
最后分享几个实际踩坑事件,每一条都对应真实的生产事故。
事故一:误用 --force 覆盖同事提交。
有一次 rebase 本地分支后,想 push 但被拒绝,我直接用了 git push --force。结果覆盖了同事刚推送的一个提交,同事 pull 时一头雾水,最后靠 reflog 才找回。复盘教训:
- 强制推送永远用
--force-with-lease,它会检查远程状态是否和上次 fetch 一致,不一致就拒绝。 - push 前先
git fetch,确认没有他人新提交。
事故二:git clean -fdx 删掉了本地证书目录。
当时想清理 node_modules 缓存,没注意本地有个 certs/ 目录是未跟踪状态,被一并删了。因为证书文件不在版本库里,无法恢复,只能找同事重新拷贝。
复盘教训:
git clean之前一定先git clean -n预览。- 对未跟踪的关键文件,要么加入版本库,要么放到版本库外的路径并定期备份。
事故三:忽略状态直接 pull,导致自动合并出问题。
某次我在本地改了 config.js,忘了提交,直接 git pull。Git 尝试自动合并,结果因为远程也改了同一文件,冲突。当时因为不知道 git stash 这个选项,手动折腾了很久。
复盘教训:
- pull 之前先处理工作区状态,要么 commit,要么 stash。
- 遇到冲突时先
git status理解当前处于 merge/rebase 状态,再决定解决策略。
这些事故最终让我形成了“先 fetch、再比对、最后动手”的习惯。Git 本身是一个宽容的工具,它给了 reflog 这样的回退机制,但真正的高手依靠的是操作前的谨慎,而不是操作后的补救。希望这些经验能帮你少走一些弯路。
