1. 安装与环境准备
1.1 Git 安装方式小结
Git 是当下开发者绕不开的工具,就算平时用 IDE 的图形按钮提交代码,底层的还是这一套命令。与其等出了问题对着错误提示干瞪眼,不如先把常用指令摸透。这篇文章没有废话,也不按什么“入门到精通”的套路来,纯粹是我这么多年干活时反复在敲的命令,从安装、配置、日常提交到分支合并、后悔药、远程推送,一条条拆开讲明白。
先收下环境这一关。不同系统装 Git 的方式不太一样,说几个我实测过最省心的:
- Linux(Debian/Ubuntu 系):
sudo apt install git - Linux(CentOS/RHEL 系):
sudo yum install git - macOS:装了 Homebrew 就直接
brew install git,没装的话去官网下 pkg 安装包也行 - Windows:推荐用 Git for Windows,它自带一个 Git Bash 终端,模拟 Linux 环境,很多命令行为跟 macOS/Linux 保持一致,踩坑少
装完先跑一句验证:
git --version能输出版本号就说明装好了。Windows 用户如果是在 CMD 里敲的,记得把 Git 的 bin 目录加到 PATH,正常安装包会帮你勾选,不用手动折腾。
1.2 首次使用前的必做配置
这一步很多人跳过去了,结果就是第一次git commit报错,或者提交记录里出现一串乱码一样的名字。Git 每次提交都会记录“谁做的”,靠的不是登录账号,而是本地的全局配置。所以开工前先配置身份:
git config --global user.name "你的名字" git config --global user.email "你的邮箱"注意这里填的邮箱最好是你代码托管平台上常用的邮箱,因为平台会把提交邮箱和你账号关联起来。我记得早年有个同事随便填了个邮箱,提交记录挂在了一个陌生账号名下,找都找不回来,非常尴尬。
还有几个我建议顺手配掉的:
git config --global init.defaultBranch main git config --global core.autocrlf input git config --global core.quotepath falseinit.defaultBranch是把新仓库的默认分支从master改成main,跟 GitHub/Gitee 新建仓库的默认保持一致,少一点心智负担。core.autocrlf是换行符策略:Windows 上建议设成true,Mac/Linux 设成input,避免出现整个文件所有行都被标记为修改的“换行符灾难”。core.quotepath false这个对中文用户特别友好,不然git status显示中文文件名时全是一堆反斜杠转义,看着头大。
查看当前配置用:
git config --list想改某一条就重新执行对应的 config 命令。配置作用域分三层:system、global、local,默认不写作用域时操作的是 local 层,它会覆盖 global 的相同配置,很适合不同项目用不同身份的情况。实际工作中我在公司电脑上就靠这个区分个人项目和公司项目。
2. 日常开发最常用的基础指令
2.1 初始化仓库与克隆远程项目
拿到一个新项目,一般就两种情况:从零开始,或者把远程已有的仓库拉下来。
从零开始,在项目目录里执行:
git init执行完目录下会多一个.git隐藏文件夹,这就是仓库的核心,所有版本历史都存这里面。这里提醒一句:千万不要手贱去删.git目录,删了等于历史全没了。
如果远程已经有人建好了仓库,用clone拉一份到本地:
git clone <仓库地址>仓库地址一般有两种形式:
- HTTPS:
https://github.com/user/repo.git - SSH:
git@github.com:user/repo.git
HTTPS 每次 push 都要输账号密码,不过可以用凭据管理器记住。SSH 需要配置密钥,后面第六章会专门说。我个人的习惯是走 SSH,一劳永逸。
clone 有个小参数我经常用:
git clone -b dev <仓库地址>这个可以指定克隆某个分支,而不是默认的 main/master。有些仓库默认分支是坏的或者不是你想用的,这个能省掉一次切分支的麻烦。
2.2 状态检查、暂存与提交
日常开发三连:status看状态,add暂存,commit提交。
git status这条一定是最高频的命令,没有之一。它会告诉你当前工作区干不干净,哪些文件改过、哪些是新文件、哪些已进入暂存区。我个人的习惯是:任何操作之前先跑一遍git status,就像你开车前先看一眼仪表盘,尤其是准备执行 reset 或 checkout 这种危险命令时,这一眼能救你命。
add是把改动放入暂存区,也就是告诉 Git“我要把这些文件纳入下一次提交”:
git add <文件名> # 暂存指定文件 git add . # 暂存当前目录所有改动 git add src/ utils/ # 暂存多个目录有些新手喜欢无脑git add .,我的建议是提交前先用git status和git diff确认一下哪些文件动了。尤其是项目里可能有本地配置文件、临时文件,无脑 add 会把不该提交的东西带进去。如果确实想全加,也要先看一遍列表,确认没有.env之类的敏感文件。
然后提交:
git commit -m "提交说明"提交说明就是 commit message,这里多说两句。写好提交信息比写好代码还重要,因为半年后你回头看历史,靠的就是这些信息。一个比较通用的格式是:
<type>: <subject>比如:
fix: 修复登录接口空指针异常 feat: 新增用户导出功能 docs: 更新 READMEtype可以是feat(新功能)、fix(修 bug)、docs(文档)、refactor(重构)、test(测试)等。这是我见过最省事的规范,既不过度工程化,又能让历史记录一眼扫明白。
2.3 提交信息的补救:git commit --amend
有没有遇到这种情况:提交完发现 message 打错字了,或者漏了一个文件没加进去?
git commit --amend它会打开编辑器让你修改上一次的提交信息。如果只是想顺带补文件,先 add 再 amend:
git add 漏掉的文件.txt git commit --amend执行完之后,上一次提交就被“覆盖”成了一个新的提交,不会多出一条历史记录。
这里有一条非常重要的红线:amend只适合处理还没有推送到远程的提交。如果这个提交已经 push 出去了,别人可能基于它做了分支,你这边一 amend,两边的提交哈希就对不上了,后面会引发一连串合并冲突。所以我的原则是:本地 commit 后发现小问题,随便 amend;已经 push 的,老老实实用git revert(后面会讲)。
3. 分支管理与远程协作
3.1 分支的创建、切换与合并
分支是 Git 相比 SVN 最大的优势。你可以理解成平行宇宙:在dev分支上改代码不会影响main,等开发完了再合并回来。
查看当前分支:
git branch创建新分支:
git branch feature-login切换分支:
git switch feature-loginGit 2.23 之后推荐用switch,语义更清晰。老一点的命令git checkout feature-login也能用,但checkout身兼数职(既能切分支又能丢弃文件改动),新手容易混淆。
更常用的是“创建并切换”一步到位:
git switch -c feature-login等于git branch feature-login+git switch feature-login。
合并分支回到主分支:
git switch main git merge feature-login如果合并时没有冲突,Git 会直接走 fast-forward,把 main 的指针挪到 feature-login 上。如果有冲突,就会进入冲突解决流程,这个在 3.3 细说。
3.2 远程仓库:remote、push、pull、fetch
本地分支搞完了,得把代码推到远程,不然合作的人看不到。
关联一个远程仓库:
git remote add origin https://github.com/user/repo.gitorigin是远程仓库的默认别名,你叫它什么都行,但行业约定俗成就是origin。
查看远程仓库列表:
git remote -v第一次推送时需要指定上游分支:
git push -u origin main-u是--set-upstream的简写,意思是“把本地的 main 分支和远程的 main 分支关联起来”。加过一次之后,后续直接敲git push就行了。
拉取远程更新:
git pull这里必须讲一个很多人栽过的坑:git pull默认执行的是fetch+merge。如果你的本地提交和远程提交出现了分叉,Git 会自动创建一个 merge commit,历史会变得很乱。更推荐的做法是:
git pull --rebase这会把你在本地还没推送的提交“摘下来”,暂存到一边,先把远程的提交拉下来,再把你自己的提交按顺序“放”上去,历史保持线性,干净很多。我自己把pull.rebase设为默认配置了:
git config --global pull.rebase true设完之后git pull就等价于git pull --rebase,省心。
3.3 合并冲突的解决思路
冲突这玩意儿,第一次遇到的人基本都会慌。别慌,它没那么可怕。
什么时候会有冲突?两个人改了同一个文件的同一个区域,Git 不知道怎么自动合并,就会把这个文件标记为冲突状态。
冲突状态的文件,打开之后长这样:
<<<<<<< HEAD 这里是当前分支的内容 ======= 这里是合并进来的分支的内容 >>>>>>> feature-login<<<<<<<和=======之间的是当前分支版本,=======和>>>>>>>之间的是被合并分支版本。你要做的就是把该留的留下,该删的删掉,包括这三种标记符号,一个都不能留。
处理完保存文件,然后:
git add 冲突解决后的文件 git commitGit 会自动生成一条 merge commit。如果你不确定怎么解决,我建议到 IDE 里看,VS Code 和 IntelliJ 系列都有可视化冲突解决界面,谁会保留、谁会丢弃,点几下就好。
有一条经验:冲突解决时先看清哪个版本是最近的。宁可少留不要乱留,把模棱两可的逻辑拿出来跟当事人对一下,千万别“凭感觉留一份”。
4. 撤销与回滚
4.1 工作区、暂存区、本地仓库三个区域的底层认知
要理解撤销操作,先得搞清楚 Git 的三个区域。我用个购物类比:
- 工作区(Working Directory):你的项目目录,相当于手里的草稿纸,改代码都是在这里发生的
- 暂存区(Index/Staging Area):相当于购物车,你把看中的商品(改动的文件)放进去,等待结算
- 本地仓库(HEAD):相当于已经结账打包回家的货架,
commit就是结算的动作
每个文件在这三个区域之间移动的状态,就是git status里看到的那些提示:
modified:工作区有改动,但还没 addstaged:已经 add,待 commitcommitted:已经 commit,和 HEAD 一致
理解了这三个区域,撤销操作就只是“把文件恢复到某个区域的状态”的问题了。
4.2 各种撤销场景对应指令表
先给一个速查表,再做详细解释:
| 场景 | 指令 | 风险程度 |
|---|---|---|
| 丢弃工作区某个文件的改动 | git restore <file>或git checkout -- <file> | 危险,改动不可找回 |
| 把暂存区的文件退回到工作区 | git restore --staged <file> | 低风险,内容不会丢 |
| 撤销上一次提交并保留改动 | git reset --soft HEAD~1 | 低风险 |
| 撤销上一次提交并取消暂存 | git reset HEAD~1(默认 mixed) | 低风险 |
| 撤销上一次提交并删除改动 | git reset --hard HEAD~1 | 极度危险 |
| 撤销某一次已推送的提交 | git revert <commit> | 安全,会生成新提交 |
不推荐用git checkout恢复文件,因为它和“切换分支”是同一个命令,容易误解。新版 Git 提供git restore,语义明确,我只推荐这个。
4.3 reset、revert、clean 的正确打开方式
先说git reset。它有三种模式,区别在于“回撤之后,改动还在不在”:
--soft:只移动 HEAD 指针,暂存区和工作区都不动。相当于提交 history 没了,但代码改动都在,等着你重新提交--mixed(默认):移动 HEAD 指针,并重置暂存区,但工作区不动。改动保留但在工作区,需要重新 add--hard:移动 HEAD 指针,同时重置暂存区和工作区。改动彻底消失,找不回来
一个我常用的场景:刚 commit 完发现里面有个多余文件,想拆成两个提交。用git reset --soft HEAD~1,把提交撤掉,但改动全部保留在暂存区,然后重新 add 拆分。
git reset --hard要慎用,它不会保留任何备份。但如果只是恢复到之前某次提交,而且你的分支已经推到远程了,可以考虑git reset --hard origin/main来“强制同步”到远程状态。
再说git revert。它不删历史,而是生成一个“反向提交”把之前的改动抵消掉。这适合已经 push 出去的提交。因为历史里会留下完整的操作记录,团队成员看到的是“有人用一次提交撤销了另一次提交”,非常透明。
我自己作为团队里经常 review 代码的人,特别反感有人对已推送的提交用reset --hard,因为一旦别人基于之前的提交继续开发,后面强行 push 会把别人辛辛苦苦推上去的提交冲掉。这个场景必须用revert。
最后提一下git clean。它清理的是工作区里未跟踪的文件,比如编译生成的临时文件。我偶尔会用:
git clean -fd-f是强制删除,-d是连同目录一起删。这个命令不会询问太多,执行前一定先看git status,确认哪些是未跟踪文件,否则误删了就凭空消失。
5. 查看历史与代码排查
5.1 git log 的常用参数,一眼看懂提交记录
Git 历史记录我用得最多的是几个参数组合:
git log --oneline --graph --decorate --all--oneline:每条提交只用一行显示,包含短哈希和提交信息--graph:用线条画出分支合并走向,网络图效果,特别适合理解分支结构--decorate:显示分支/标签指向了哪个提交--all:显示所有分支,而不只是当前分支
还可以带作者过滤和时间过滤:
git log --author="张三" --since="2024-01-01" --until="2024-06-01"这个我在做月度复盘或者代码审计时经常用,能快速统计某个人在某段时间的提交量。
想搜索某段代码是哪个提交引入的:
git log -S "某段字符串"-S也叫“pickaxe”,它会找出新增或删除这个字符串的所有提交。定位 bug 是谁引入的,这个命令比人肉翻代码高效一百倍。
查看某次提交的改动内容:
git show <commit哈希>5.2 用 blame 定位“罪魁祸首”
git blame是我排查问题时的神器。它可以显示文件每一行最后被谁修改、在哪次提交里改的:
git blame 文件名只看某几行:
git blame -L 100,120 文件名看到某一行是问题代码,但不知道为什么这么写时,拿提交哈希去问当事人是最直接的。当然,blame不是为了“追责”,而是为了理解代码演化过程。很多时候你看到一段奇怪的代码,骂骂咧咧点进去一查,发现是半年前你自己写的,那体验可酸爽了。正因为这样,我养成了写提交信息时把“为什么这样改”也写进去的习惯,能帮未来的自己和同事省很多时间。
5.3 用 diff 看清楚改动细节
代码审查和提交之前,git diff是必备的一步。
- 查看工作区相对暂存区改了什么:
git diff - 查看暂存区相对上次提交改了什么:
git diff --cached(或--staged) - 查看两个分支之间的差异:
git diff 分支A 分支B - 查看某次提交改了什么:
git show <哈希>
特别是git diff --cached,提交前扫一眼,防止把调试代码、敏感信息、无意义的空格变化一起提交上去。我有个坏毛病就是喜欢改完代码顺手多敲几个空格,这种噪音会让diff变得很难看,如果被 reviewer 看到,虽然没问题但阅读体验很差。所以提交前我会挨个文件看 diff,把不该有的格式调整拆到单独的 commit 里。
6. SSH 密钥配置与远程推送实战
6.1 生成密钥并添加到代码托管平台
前面说过 SSH 方式推送可以免输密码,这里详细走一遍。
首先生成密钥对。我推荐用 ed25519 算法,生成快、安全性强:
ssh-keygen -t ed25519 -C "你的邮箱@example.com"一路回车把文件保存在~/.ssh/下。如果你之前已经生成过 RSA 密钥,不想覆盖它,可以在提示输入文件名时改个名,比如id_ed25519_gitee。
生成的公钥文件是~/.ssh/id_ed25519.pub,查看内容:
cat ~/.ssh/id_ed25519.pub复制输出的一整段内容,去代码托管平台(GitHub 或 Gitee)的设置页面,找到“SSH Keys”,粘贴保存。
验证是否配置成功:
ssh -T git@gitee.com第一次连接会提示你确认 host key,输yes。成功后平台会返回一段欢迎语,看到类似 “Hi xxx! You've successfully authenticated” 就成了。
6.2 手动关联远程仓库并推送
在托管平台上新建一个空仓库,然后把本地代码推上去。步骤:
git init git add . git commit -m "初始提交" git remote add origin git@gitee.com:你的用户名/仓库名.git git push -u origin main如果仓库里已经有 README 等文件,直接 push 会失败,提示远程包含本地不存在的提交。这时要么先git pull --rebase origin main,要么干脆建空仓库不勾选“初始化 README”。
第一次 push 一个大项目时会比较慢,属于正常现象。后续再推送就只是一些增量数据了。
6.3 多平台多密钥管理的实际经验
如果你同时用 GitHub 和 Gitee,甚至还有公司内部的 GitLab,同一对密钥是不能跨平台用的(不同平台的账号体系不同),也不建议所有平台都用同一个密钥。正确做法是给每个平台生成不同的密钥,然后在~/.ssh/config里做匹配:
Host github.com HostName github.com User git IdentityFile ~/.ssh/id_ed25519_github Host gitee.com HostName gitee.com User git IdentityFile ~/.ssh/id_ed25519_gitee这样 SSH 连接时会按 Host 自动选择对应的密钥文件,不用每次手动指定。我最早没有配这个,为了图省事把同一个密钥加到两个平台,后面发现有人在 GitHub 上用我的身份推送代码,吓得我赶紧把密钥撤掉重新配。密钥泄漏的排查很麻烦,所以从一开始就分别管理是正确的姿势。
7. 高频问题速查与避坑心得
| 问题 | 原因 | 解决方式 |
|---|---|---|
| push 被拒绝,提示 non-fast-forward | 远程有新提交本地没有 | git pull --rebase后重新 push |
| 每次 push 都要输密码 | 用了 HTTPS 且未缓存凭据 | 配置 SSH 密钥或用git config --global credential.helper store(注意安全性) |
| 提交信息写错了 | 手滑 / 忘改 | git commit --amend修改最近一次 |
| 不小心提交了敏感文件 | 误将.env等加入暂存区 | 立即从历史移除,参考git filter-branch或第三方工具,并立刻去平台撤销/更换密钥 |
| 误删了本地分支 | git branch -D删错了 | 用git reflog找到分支指向的哈希,git branch <名字> <哈希>重建 |
| 文件中文名显示乱码 | 核心配置缺少core.quotepath false | 执行git config --global core.quotepath false |
| 合并冲突出现一堆重复代码 | 换行符混乱 | 统一core.autocrlf配置 |
git reflog这条值得单独说一下。它记录的是 HEAD 指针每次移动的历史,相当于 Git 的操作“黑匣子”。哪怕你对一个分支执行了git reset --hard,只要 reflog 里还有记录,就能找回之前的状态。我吃过一次亏之后,现在每次执行破坏性命令前都会快速看一下 reflog 和 status。操作流程是:
git reflog git branch 救援分支 <目标哈希>reflog的日志不会永久保留,默认过期时间是 90 天,但关键时刻就是救命的稻草。
还有一条经验:在团队协作里,不要对已经推送的分支做amend或reset --hard。如果你真的做了,那被强行覆盖的同事基本要骂人了。已经推送的代码要修改历史,走revert,留一条完整的记录,比“悄悄改写历史”要干净得多。
最后讲一个我踩过好多次的坑:提交前不看 status 和 diff。尤其是项目比较大时,随手git add .,结果把自己配了一下午的本地调试配置也提交上去了。后来我给自己定了一条规则:
执行任何会影响代码的命令之前,必须先看一遍
git status和git diff,确认改动范围完全在自己预期内。
这是 Git 使用中最简单同时也是最有效的一条自律。命令行确实高效,但效率不应该建立在侥幸之上。命令那么多,真正能记住的其实不多,我这些年也只用其中不到十个,但把每个用熟练、用对场景,远远好过背熟一百个却总在重要时刻把分支搞乱。
如果你刚接触 Git,建议从status、add、commit三件套练起,然后把log、branch、merge玩顺,最后再碰reset、revert这类危险操作。一切顺利之后,记得常备reflog这个守护神,它会在你最慌乱的时候给你兜底。