Git 必备命令指南:从日常高频到项目开发实战
无论你是独自开发一个 side project,还是在十几人的团队里协作,Git 都是绕不开的工具。问题在于:Git 的命令多达上百条,真正天天用 的其实只有一小撮,而真正决定你能不能在项目里不翻车的,又是另外一小撮。
这篇文章把 Git 命令分成三层来讲:
- 第一层:最基本常用命令 ------ 你每天会敲几十遍的那些,配场景。
- 第二层:项目开发最重要的命令 ------ 分支、合并、回退、暂存、冲突处理,这些是团队协作里"会不会用 Git"的分水岭。
- 第三层:典型团队工作流 ------ 把零散的命令拼成一套协作流程。
最后附一张速查表和几条避坑经验。读完它,你应对 95% 的日常开发场景应该都不用再查文档。
零、先做一次配置(一劳永逸)
装好 Git 之后,第一件事是告诉它你是谁。因为每一次提交都会带上你的姓名和邮箱,团队协作时这个信息会出现在 git log 和 GitHub/GitLab 的提交记录里。
bash
git config --global user.name "你的名字"
git config --global user.email "you@example.com"
几个强烈建议一并设置的项:
bash
# 默认分支名改为 main(GitHub 已是默认)
git config --global init.defaultBranch main
# 让 git pull 默认用 rebase,历史更干净
git config --global pull.rebase true
# 让 push 默认只推当前分支
git config --global push.default current
# 彩色输出
git config --global color.ui auto
# 记住一次就免重复输入账号密码(HTTPS 场景)
git config --global credential.helper osxkeychain # macOS
git config --global credential.helper cache # Linux 临时缓存
查看所有配置:
bash
git config --list
小贴士:去掉
--global就是只对当前仓库生效(项目级覆盖全局级)。公司项目用一个邮箱、个人项目用另一个邮箱时很有用。
第一层:最基本常用命令(日常高频)
这一节的命令,你会用到肌肉记忆的程度。
1. 拿到代码:clone 与 init
克隆一个已有仓库(最常见,从 GitHub 上拉项目):
bash
git clone https://github.com/user/repo.git
# 只想克隆最近一次提交,省带宽
git clone --depth 1 https://github.com/user/repo.git
从零创建一个本地仓库:
bash
mkdir my-project && cd my-project
git init
2. 看清楚现在的状态:status
这是你最该频繁敲的命令,没有之一。它告诉你:改了哪些文件、哪些已暂存、哪些还没暂存、当前在哪个分支、是否领先/落后远程。
bash
git status
# 简洁版
git status -s
3. 暂存与提交:add / commit
Git 的提交分两步:先把改动放到暂存区(staging area) ,再提交成一个快照。
bash
# 暂存单个文件
git add README.md
# 暂存所有改动(包括新增、修改、删除)
git add -A
# 或等价的简写(当前目录及其子目录)
git add .
# 交互式选择要暂存的代码块(强烈推荐,写过的代码也能只挑一部分提交)
git add -p
提交:
bash
git commit -m "修复登录页空指针崩溃"
# 想写多行、更规范的提交信息
git commit
一条好的提交信息长这样(Conventional Commits 约定):
scss
fix(auth): 修复 token 过期后无法刷新的问题
清除定时器时漏掉了 refresh 调用,导致 401 后白屏。
4. 看历史与差异:log / diff
bash
# 经典日志
git log
# 单行 + 图形化 + 显示分支(最常用)
git log --oneline --graph --all
# 只看最近 5 条
git log -5
# 看某个文件的修改历史
git log -- path/to/file.ts
看未提交的改动:
bash
git diff # 工作区 vs 暂存区(还没 add 的改动)
git diff --staged # 暂存区 vs 上次提交(已 add、将要提交的改动)
git diff HEAD # 工作区 vs 上次提交(所有未提交的改动)
5. 与远程同步:push / pull / fetch
bash
# 把本地提交推到远程
git push
# 首次推送一个新分支到远程
git push -u origin feature/login
# 拉取远程更新并合并到当前分支
git pull
# 只把远程更新下载下来,不合并(更安全,可以自己决定怎么合)
git fetch
默认情况下 pull = fetch + merge;如果你像前面那样设置了 pull.rebase true,则变成 fetch + rebase,历史更干净。无论哪种,都养成"先 fetch/pull 再 push"的习惯,能避免大部分冲突。
第二层:项目开发最重要的命令
到这里往后,是区分"会用 Git"和"Git 用得好"的关键。
1. 分支管理:branch / switch / checkout
分支是 Git 的灵魂。团队开发的基本规则是:永远不要在 main 上直接写代码,每个功能开一个分支。
bash
# 查看所有本地分支(* 是当前分支)
git branch
# 查看所有分支(含远程)
git branch -a
# 创建新分支(但不切换过去)
git branch feature/login
# 切换分支(Git 2.23+ 推荐用 switch,语义更清晰)
git switch feature/login
# 老写法,仍然能用
git checkout feature/login
# 一步到位:基于当前分支创建并切换
git switch -c feature/login
# 老写法
git checkout -b feature/login
# 删除已合并的分支
git branch -d feature/login
# 强制删除(即使没合并)
git branch -D feature/login
实战场景 :你想基于远程的 develop 分支开个新功能,而不是基于本地的 main:
bash
git fetch origin
git switch -c feature/payment origin/develop
2. 合并与变基:merge / rebase
把分支的代码并回主线,有两条路。
bash
# 合并:把 feature 合进当前分支,会产生一个 merge commit
git switch main
git merge feature/login
# 变基:把 feature 上的提交"嫁接"到 main 最新位置之后,历史是一条直线
git switch feature/login
git rebase main
两者怎么选?
merge保留完整的分支历史,是谁在哪个分支上做的、何时合并的,一目了然。适合把功能分支并回主线。rebase让提交历史干净成一条线,但会改写提交历史(hash 变了)。适合在 push 之前整理自己的功能分支。
黄金法则:永远不要对已经 push 出去、别人可能基于它工作的分支做 rebase。 否则你会把同事的本地仓库搞乱。
rebase 的交互模式是整理提交历史的神器:
bash
git rebase -i HEAD~4 # 整理最近 4 次提交
它会打开编辑器,让你对每一次提交做:合并(squash)、改写信息(reword)、调整顺序、丢弃等。
只想搬某一次提交过来?用 cherry-pick:
bash
# 把指定的提交"摘"过来,复制到当前分支顶部
# 常见场景:在 main 修了个 hotfix,再把它同步到 develop / 旧版本分支
git cherry-pick <commit-id>
merge/rebase是把整条分支 并过来,cherry-pick是只搬单个提交,三者按需选用。
3. 解决冲突
当两个分支改了同一处代码,Git 无法自动合并时,就会产生冲突。文件里会出现:
markdown
<<<<<<< HEAD
这是当前分支的内容
=======
这是被合并进来的内容
>>>>>>> feature/login
处理流程:
bash
git merge feature/login # 提示冲突
git status # 查看哪些文件冲突
# 手动编辑文件,删掉 <<<<<<< ======= >>>>>>> 标记,保留正确内容
# 改完后标记已解决
git add 冲突文件
git commit # 完成合并提交
# 如果改到一半想放弃合并
git merge --abort
如果冲突发生在 rebase 过程中 ,处理方式几乎一样(编辑文件 → git add),只是收尾命令不同------这是最容易记混的一点:
bash
# 解决完当前冲突,继续变基(注意:这里不要用 git commit)
git add 冲突文件
git rebase --continue
# 想放弃整个变基,回到 rebase 前的状态
git rebase --abort
# 想跳过当前这个冲突的提交
git rebase --skip
冲突不可怕,怕的是乱删。原则是:读懂两边的意图,手动拼出正确结果,而不是简单删掉一边。
4. 撤销与回退:Git 的"后悔药"
这是最容易出错、也最该学透的一块。先记住一个心智模型------一次改动会依次穿过三个区域,走到哪一步、就用哪条命令把它撤回来:
perl
add commit push
工作区 ──→ 暂存区 ──→ 提交历史 ──→ 远程仓库
│ │ │
restore restore --staged reset / revert
(已 push 必须用 revert)
先用这张表对号入座(细节看下面的场景):
| 改动走到了哪一步 | 撤销命令 | 是否影响别人 |
|---|---|---|
还没 add(在工作区) |
git restore <file> |
否 |
已经 add(进了暂存区) |
git restore --staged <file> |
否 |
已经 commit(本地) |
git reset --hard <commit> / git revert |
否(还没 push) |
已经 push(到了远程) |
git revert <commit>(反向提交) |
是,必须用 revert |
场景 A:改坏了工作区的文件,还没 add,想丢弃改动
bash
# 丢弃单个文件的工作区改动(Git 2.23+ 推荐)
git restore 文件名
# 老写法
git checkout -- 文件名
# 丢弃所有工作区改动
git restore .
# 只丢弃部分代码块(和 git add -p 对称,精细撤销)
git restore -p
场景 B:已经 add 了,想撤出暂存区(不丢内容)
bash
git restore --staged 文件名
# 老写法
git reset HEAD 文件名
场景 C:已经 commit 了,想修改最近一次提交
bash
# 追加改动到上一次提交,或只改提交信息
git commit --amend
⚠️
amend会改写提交历史 。如果这次提交已经推到共享分支(如main/develop),尽量不要 amend ,否则同事拉取会撞冲突。只有自己的功能分支、且确认没人基于它工作时才能强推,而且务必用--force-with-lease而非--force------它会在远程有别人新提交时拒绝推送,不会覆盖同事的代码:
bash
git push --force-with-lease
场景 D:想回退到某个历史版本
这里 reset 和 revert 是两个完全不同的东西,务必分清:
bash
# reset:把当前分支指针"倒退"到指定提交,之后的提交会消失
git reset --hard <commit-id> # 工作区也一起清空(危险!改动全没了)
git reset --soft <commit-id> # 只移动指针,改动全保留在暂存区
git reset --mixed <commit-id> # 移动指针,改动退回工作区(默认)
# revert:生成一个"反向提交"来抵消某次改动,不改写历史
git revert <commit-id>
怎么选?
- 改动还没 push ,想抹掉重来 →
reset。 - 改动已经 push、别人在用这个分支 → 用
revert,安全,不破坏历史。
git reset --hard是 Git 里最危险的命令之一,敲之前想清楚:丢掉的东西能不能用git reflog找回来(见文末技巧)。
5. 临时藏起改动:stash
你正在 feature 分支写代码写了一半,突然要切到 main 修个紧急 bug,又不想提交半成品。stash 就是干这个的。
bash
# 把已跟踪文件的未提交改动临时存起来,工作区变干净
# 注意:默认不含 untracked(新建、还没被 git 管理过)的文件
git stash
# 想连未跟踪的新文件一起存,加 -u
git stash -u
# 存的时候起个名字
git stash push -m "登录页改了一半"
# 查看存了哪些
git stash list
# 取出来继续干(默认取最近一次)
git stash pop
# 取出来但不从 stash 列表删除
git stash apply
# 删除某次 stash
git stash drop stash@{0}
典型工作流:
bash
git stash # 藏起 feature 上的半成品
git switch main # 切走修 bug
# ... 修复、提交、推送 ...
git switch feature
git stash pop # 把半成品取回来,接着写
6. 打标签:tag
标签用来标记发布版本 ,比如 v1.0.0。它是一个固定的、不会移动的指针,比 commit id 好记。
bash
# 给当前提交打轻量标签
git tag v1.0.0
# 打带说明的标签(推荐,相当于一个正式的发布记录)
git tag -a v1.0.0 -m "首个正式版本"
# 给某个历史提交补打标签
git tag -a v0.9.0 <commit-id>
# 查看所有标签
git tag
# 推送标签到远程(push 默认不带标签!)
git push origin v1.0.0
git push origin --tags # 一次推全部
7. 管理远程仓库:remote
bash
# 查看已配置的远程仓库
git remote -v
# 添加一个远程(比如 fork 后关联上游仓库)
git remote add origin https://github.com/you/repo.git
git remote add upstream https://github.com/original/repo.git
# 修改远程地址
git remote set-url origin git@github.com:you/repo.git
# 删除远程
git remote remove upstream
Fork 工作流 常用:定期从 upstream(原仓库)同步更新:
bash
git fetch upstream
git switch main
git merge upstream/main
git push origin main
8. 追查"这行代码是谁写的":blame
线上出 bug,想知道某行代码是谁、在哪个提交里加的,blame 逐行显示每行代码的最后修改者和提交:
bash
git blame path/to/file.ts
# 只看第 10-20 行
git blame -L 10,20 path/to/file.ts
不是为了甩锅,而是为了找到对应的提交,
git show <commit-id>看完整改动上下文,理解这段代码为什么这么写。
9. 引用独立仓库:submodule
当项目需要复用另一个独立的 Git 仓库(比如公共组件库、内部 SDK、共享的配置仓库),又不想把它的代码直接拷进来(那样就没法独立更新、也无法保留它自己的历史),就用 submodule(子模块)。
它的本质是:在你的仓库里,嵌套记录另一个仓库的「某个特定提交」。主仓库保存的不是子模块的全部内容,而是一个指向子模块某次提交的指针。
① 添加一个子模块:
bash
git submodule add https://github.com/team/ui-components.git libs/ui
这会做三件事:把 ui-components 克隆到 libs/ui、生成一个 .gitmodules 文件(记录 URL 和路径)、并把这两项改动放进暂存区。你需要把它们提交掉:
bash
git commit -m "添加 ui-components 子模块"
② 克隆一个含子模块的项目:
普通的 git clone 不会自动拉取子模块,子模块目录会是空的。需要额外一步:
bash
# 方式一:克隆时一并初始化
git clone --recurse-submodules https://github.com/you/repo.git
# 方式二:已经克隆过了,补拉子模块
git submodule update --init --recursive
--recursive 是因为子模块本身可能还嵌套着它自己的子模块。团队协作时这一步最容易被忘记,新人拉下来发现目录是空的就是这个原因。
③ 更新子模块到它的最新版本:
子模块默认「钉」在添加时指定的那个提交上。子模块仓库发布了新版本,你想跟上来:
bash
# 偷懒写法:把所有子模块都拉到各自远程的最新提交
git submodule update --remote
# 或手动控制:进到子模块里拉取,再回主仓库记录新指针
cd libs/ui
git switch main && git pull
cd ../..
git add libs/ui
git commit -m "升级 ui-components 子模块到最新"
关键认知:主仓库并不关心子模块的分支,只关心它指向哪个 commit。 所以上面的操作本质是在主仓库提交了一次「子模块指针的移动」。
④ 修改子模块里的代码:
在 libs/ui 里改、提交、推送,和普通仓库完全一样。唯一要注意的是:先切到一个分支,否则你会处于 detached HEAD(游离头指针)状态,提交容易丢失。
bash
cd libs/ui
git switch -c fix/button-color # 先切分支,避免 detached HEAD
# 改代码 → commit → push 到子模块自己的远程
git push
cd ../..
git add libs/ui
git commit -m "主仓库:子模块修复按钮颜色"
⑤ 删除子模块(比添加麻烦,记住这几步):
bash
git submodule deinit -f libs/ui # 1. 取消初始化
git rm -f libs/ui # 2. 从工作区和暂存区移除
rm -rf .git/modules/libs/ui # 3. 删除 git 内部的元数据
git commit -m "移除 ui-components 子模块"
submodule 的几个常见坑:
- 克隆忘记加
--recurse-submodules→ 目录是空的,用git submodule update --init --recursive补救。- 切换主仓库分支后子模块「没跟上」,状态显示 dirty →
git submodule update把指针移到位。- 团队协作时,主仓库里「子模块的新指针」和
.gitmodules必须一起提交推送,否则同事拉下来会对不上。- 如果只是想用一个库、并不打算跟踪它的更新,考虑用包管理器(npm / Gradle / CocoaPods)替代 submodule,往往更省心。
第三层:典型团队工作流
命令是零件,工作流是蓝图。下面是最常用的两种。
GitHub Flow(轻量,适合持续部署)
bash
main(永远可发布)
├── feature/login → 写完发 PR → review → 合并回 main
└── feature/payment → ...
git switch -c feature/xxx开分支- 写代码,频繁
commit git push -u origin feature/xxx- 在平台上发起 Pull Request
- review 通过后合并(通常用 squash merge 让历史干净)
- 删掉本地分支
git branch -d feature/xxx,git pull同步
Git Flow(重量,适合有明确版本发布的项目)
main:生产环境develop:日常开发集成feature/*:功能分支,从develop切出,合回developrelease/*:发布准备分支hotfix/*:紧急修复,从main切出,同时合回main和develop
中小团队通常用 GitHub Flow 就够了,别过度设计。
速查表
| 场景 | 命令 |
|---|---|
| 配置用户名 | git config --global user.name "名字" |
| 配置邮箱 | git config --global user.email "邮箱" |
| 克隆仓库 | git clone <url> |
| 查看状态 | git status / git status -s |
| 暂存改动 | git add . / git add -p |
| 提交 | git commit -m "msg" |
| 看历史 | git log --oneline --graph --all |
| 看改动 | git diff / git diff --staged |
| 推送/拉取 | git push / git pull |
| 新建并切换分支 | git switch -c <branch> |
| 合并分支 | git merge <branch> |
| 变基 | git rebase main / git rebase -i HEAD~3 |
| 摘取单个提交到当前分支 | git cherry-pick <commit> |
| 丢弃工作区改动 | git restore <file> |
| 精细丢弃部分工作区改动 | git restore -p |
| 撤出暂存区 | git restore --staged <file> |
| 修改最近一次提交 | git commit --amend |
| 安全回退已 push 的提交 | git revert <commit> |
| 抹掉未 push 的提交 | git reset --hard <commit> |
| 临时存改动 | git stash / git stash pop |
| 打版本标签 | git tag -a v1.0.0 -m "..." |
| 看每行代码归属 | git blame <file> |
| 添加子模块 | git submodule add <url> <path> |
| 克隆含子模块的项目 | git clone --recurse-submodules <url> |
| 初始化 / 补拉子模块 | git submodule update --init --recursive |
| 更新子模块到最新 | git submodule update --remote |
几条避坑经验
1. 永远不要在 main 上直接干活。 开分支成本几乎为零,但在 main 上写废了回退成本极高。
2. 提交要小而频繁,信息要能看懂。 半天攒一大坨、信息写 update 的提交,三个月后没人看得懂(包括你自己)。
3. push 之前先 pull / fetch。 能把冲突消灭在本地,而不是推到远程后炸掉 CI。
4. 不要对共享分支做 rebase 和 push --force。 用 --force-with-lease 替代 --force,它会在远程有别人新提交时拒绝推送,避免覆盖同事的代码。
5. reset --hard 之前先 git stash 或 git branch backup。 给自己留个后悔的余地。
6. 误删了提交?git reflog 是救命稻草。 它记录了 HEAD 的每一次移动,哪怕 reset --hard 抹掉的提交,只要没被垃圾回收,都能用 git reflog 找到对应 id,再 git reset --hard <id> 回去:
bash
git reflog
git reset --hard <reflog 里的 commit-id>
结语
Git 命令虽多,但日常开发真正高频的就十来条,团队协作里关键的也集中在分支、合并、回退、冲突这几块。把第一层用成肌肉记忆,把第二层理解透背后的"工作区/暂存区/提交历史"模型,你就已经超过大多数开发者了。
剩下的高级命令(cherry-pick、bisect、worktree 等),等遇到对应场景再查也来得及。工具是为流程服务的------先建立好工作习惯(小步提交、规范分支、写清楚信息),Git 自然会成为你的帮手,而不是负担。