Git 这个东西,说实话刚接触的人容易把它当成一个“上传代码的网盘工具”,用着用着就会发现不对劲,怎么动不动就冲突了,怎么改个文件还要先 add 再 commit,怎么 reset 一下代码没了。我见过太多新人在这一步就劝退了,但其实 Git 的设计逻辑捋顺了之后非常顺,它解决的从来不只是“存代码”,而是让你在改代码这件事上能随便折腾、随时反悔、多人并行不打架。
这篇东西我不打算搞成一本正经的文档式教程,就按我平时带新人、帮同事擦屁股的实际路径来讲讲 Git 的核心东西。内容包括 Git 到底在解决什么问题、安装好之后第一件事要配置什么、每天高频用的命令怎么理解、分支怎么玩才不乱、多人协作时怎么避免互相伤害、以及改错了怎么安全地退回去。看完你至少能明白自己在敲什么、为什么这么敲、出了问题去哪里找答案。
1. 先搞清楚 Git 到底在解决什么问题
1.1 从“网盘式备份”到“版本快照”
很多人一开始管理代码是这么搞的:项目文件夹复制一份,改名为project_final,改两天后又复制一份,改名为project_final_v2,再改出问题了想回退,结果发现 v2 和 final 都不知道差在哪。这就是典型的没有版本管理意识。
Git 的核心思路其实特别朴素:每次你觉得代码到了一个“可以记录”的点,就拍一张快照,Git 把整棵文件树的状态完整记录下来。之后你可以随时在任何两张快照之间来回跳,也可以把某一次改动单独拿出来看,甚至可以创造出一条完全独立的时间线去“实验”想法,成功了再合并回来,失败了直接丢掉。
这个思想和网盘同步有本质区别。网盘帮你存的是“当前最新状态”,Git 帮你存的是“整个演变历史”。所以你在 Git 里做的几乎所有操作,本质上都是在操作这条历史链条:新增一个节点、回到某个节点、对比两个节点的差异、把两条链合并到一起。
1.2 三个区域:工作区、暂存区、版本库
理解 Git 绕不开这三个概念,这是整个工具的地基。
- 工作区:就是你电脑上肉眼可见、编辑器里正在改的那些文件。
- 暂存区:你可以把它理解为一个“候车区”。你告诉 Git“我要把这几件事记到历史里”,但还没正式盖章,这些改动就先放在暂存区。
- 版本库:一旦 commit,改动就正式生成一个快照节点,进入 Git 的版本历史里,永久(只要你不主动搞破坏)保留。
很多人第一次用 Git 会疑惑,我明明git add了,怎么git commit之后git status还是有东西?大概率是改了文件之后忘了重新 add。这是新手最常见的“灵魂拷问”来源之一。养成一个肌肉记忆:每次 commit 前先git status确认一下到底提交了什么,再动手。
1.3 Git 是“快照”而不是“差异补丁”
SVN 时代版本库存的是相邻两个版本的差异,要还原某个版本需要从起点开始不断叠加补丁。Git 则相反,每个 commit 节点都保存了那一时刻全部文件的一个引用,切分支、看历史都极其快。
这就解释了为什么 Git 的很多操作看着“重”,实际上非常轻——因为它存的是快照的引用关系,而不是真的把每个版本的文件都复制一份塞满磁盘。这个思想理解了,后续很多命令行为你都不会觉得奇怪。
2. 安装与初始化:不要小看第一步
2.1 各平台的安装方式
说实话安装 Git 本身没什么技术含量,但这一关劝退了很多人,尤其是 Windows 用户一看到一堆安装选项就懵。
- Windows:去 Git 官网下载安装包,一路默认即可。唯一建议留意的是安装过程中“调整 PATH 环境变量”那一步,默认选项就行,选错了可能导致命令行里找不到 git。
- macOS:终端里敲
git --version,如果没装,系统会弹窗引导你安装 Command Line Tools。或者你有 Homebrew 的话,brew install git也算省事。 - Linux:Debian/Ubuntu 系用
sudo apt install git,CentOS/RHEL 系用sudo yum install git,没什么好说的。
装完之后建议顺手做一件事:关掉当前终端,重新开一个,敲git --version确认输出正常。这一步能避免不少“我明明装了怎么提示找不到”的尴尬。
2.2 安装后的第一件事:配置身份
这一步可能比安装本身更重要,因为Git 在每次 commit 的时候会把用户名字和邮箱写进历史记录,而且是写死的那种。等到你提交完代码发现作者名是whatever或者邮箱是空的,再想改就得动用改写历史的命令,麻烦得很。
打开终端,依次执行三行配置:
git config --global user.name "你的名字" git config --global user.email "你的邮箱" git config --global init.defaultBranch main前两条大家都能理解,第三条是设置新建仓库时默认主分支名字。近些年社区普遍把默认分支从 master 改成了 main,提前设置好,后面新建仓库就不会在这个细节上纠结。
这里有个小知识点,--global表示这台机器上的所有仓库都用这个配置。如果你有一些项目需要单独用不同的身份(比如工作项目用公司邮箱,个人项目用个人邮箱),就在对应的仓库目录下执行不带--global的同款命令,Git 会优先使用仓库级配置。
2.3 配置 SSH Key 不是必须,但建议提前做
如果你只是在自己电脑上本地用 Git,完全可以跳过这一节。但只要你打算往 GitHub、GitLab、Gitee 这类远程仓库上推代码,配置 SSH Key 能让你省掉大量“每次输入用户名密码”的烦躁。
整个流程三步走:
- 生成密钥对:
ssh-keygen -t ed25519 -C "你的邮箱"一路回车即可,它会生成一对公私钥,默认存放在~/.ssh/目录下。
- 查看公钥内容:
cat ~/.ssh/id_ed25519.pub- 把输出的一整行内容复制到远程仓库平台(GitHub/GitLab/Gitee 等)的 SSH Keys 设置页面里。
配好之后,clone 远程仓库时选择 SSH 地址而不是 HTTPS 地址,后面 push/pull 就不用每次输入账号密码了。实测下来这个体验差距非常大,尤其是高频推送代码的时候。
3. 每天用得最多的核心命令
3.1 从零起步:init、add、commit 的完整链路
不管你是新建项目还是接手已有代码,日常高频链路其实就这么几条。
进入项目目录,初始化仓库:
git init这个命令会在当前目录生成一个隐藏的.git文件夹,从现在开始这个目录的一切变化都会被 Git 追踪。注意一点,不要手动去改.git文件夹里面的东西,出问题恢复起来很麻烦。
接着把文件加入暂存区并提交:
git add . git commit -m "初始化项目"git add .的作用是把当前目录下所有变动过的文件加入暂存区。.是一种通配符写法,代表当前目录。如果你只想提交某一个文件,用git add 文件名即可。
然后git commit -m后面跟的引号内容,就是这个历史节点你写给未来自己/队友看的“改动说明”。我强烈建议每个 commit 都写清楚“做了什么、为什么这么做”,不要用update、fix、asdf这种废话提交信息。等过两周你回来看历史,你会感谢当初好好写说明的自己。
3.2 查看状态与历史记录
git status是你平时用得最多的命令,没有之一。它告诉你三件事:当前在哪个分支、哪些文件被改过、哪些改动已经暂存。每次操作前养成敲一下的习惯,基本可以避免九成误操作。
git log查看提交历史:
git log --oneline --graph --all--oneline让每条记录只显示一行摘要,--graph用字符画的方式显示分支走向,--all显示所有分支而不是只有当前分支。这三个参数组合在一起,是我个人最推荐的历史查看方式,信息够用且直观。
3.3 对比差异:diff 的正确用法
git diff是检查“改了什么”的命令,它对于排查一个莫名其妙的问题特别有用。
git diff # 查看工作区与暂存区的差异 git diff --staged # 查看暂存区与上一次提交的差异 git diff HEAD # 查看工作区与最近一次提交的差异新手最容易混淆的是前两个。简单记忆方法:不带参数的 diff 看的是你“还没 add 什么”,带--staged的看的是你“已经 add 了什么”。
4. 分支管理:让代码“并行”成为可能
4.1 分支到底是什么
一句话解释分支:它是一条独立的提交时间线。在 main 分支上创建出一个新分支,这两个分支上的提交互不影响,你可以在新分支上随便折腾,折腾完了再合并回主分支,折腾砸了直接删除分支,主分支毫发无损。
理解这一点后,很多项目规范你就能看懂为什么了:开发新功能开feature/xxx分支、修紧急 bug 开hotfix/xxx分支、坚决不在 main 分支上直接改代码。这些规则的本质不是搞形式主义,而是保证主分支永远处于一个可发布的状态。
4.2 分支的创建、切换与合并
最常用的分支操作如下:
git branch 新分支名 # 创建分支 git checkout 分支名 # 切换分支 git checkout -b 新分支名 # 创建并切换(高频用法) git branch -d 分支名 # 删除分支合并分支的核心命令是:
git merge 要合并进来的分支名假设你在feature/login分支上开发完登录功能,想合并回main,步骤是:
git checkout main切回主分支git merge feature/login把功能分支合并进来- 如果没问题,删掉
feature/login分支收工
这里有个新手的常见困惑:合并前要不要先 pull 最新代码?答案是要。在开始一天的开发前,或者准备合并分支前,先把目标分支拉到最新状态,能减少大量冲突。
4.3 合并时机与方式的选择
git merge会保留两条分支的分叉历史,看着像一张网;如果开发过程中分支不多,也可以用git rebase把一条分支的提交“搬到”另一条分支的顶端,让历史变成一条直线。
我的建议是:
- 自己随意折腾的分支,rebase 更清爽
- 多人共享的分支,主角必须是 merge,尽量别擅自 rebase
原因是 rebase 会原封不动地“重放”提交,如果那条分支已经被别人拉下去用了,你 rebase 之后 push,别人再 pull 就会遇到历史分叉不一致的问题,那种局面真的很难解释清楚。
关于合并冲突,很多人谈之色变,其实是正常现象。当两个分支改了同一个文件的同一行时,Git 不知道你想保留哪个,只能停下来让你人工裁决。冲突文件里会看到这样的标记:
<<<<<<< HEAD 这是当前分支的内容 ======= 这是要合并进来的内容 >>>>>>> feature/login解决方式不复杂:编辑文件,删掉<<<<<<<、=======、>>>>>>>这些标记行,把内容改成你最终想要的版本,然后git add这个文件,再git commit,合并就完成了。我见过不少新人在这里手忙脚乱,其实放宽心,冲突只是意味着“需要人来判断”,而不是“出事故了”。
5. 远程协作:把代码推到远端
5.1 建立远程连接:remote 与 clone
git remote add origin 远程仓库地址用于把本地仓库与远程仓库关联起来,origin只是远程仓库的默认命名,不是硬编码。
git clone 远程仓库地址则会把远程仓库完整复制到本地,包括所有分支和历史记录,同时自动配置好 remote。所以接手一个已有项目,通常直接git clone就完事。
这里有一个实操细节值得提一下:克隆下来的仓库默认只会在本地给你建一个当前分支的跟踪关系,想查看全部远程分支,用git branch -r能看到远程有哪些分支。真正要切到某个远程分支开发,一般git checkout 分支名就行,Git 会自动帮你创建本地分支并追踪对应的远程分支。
5.2 push 与 pull:两个方向的同步
把本地提交推送到远程:
git push origin 分支名如果你是第一次推送这个分支,并且想让本地分支和远程分支建立跟踪关系,可以用:
git push -u origin 分支名-u是--set-upstream的简写,设置一次之后,后续在这条分支上直接敲git push和git pull就可以了,不用再重复指定远程仓库和分支名。
拉取远程更新到本地:
git pull注意git pull在底层其实是git fetch加git merge的组合。git fetch只是把远程的最新状态下载到本地,但不动你当前的工作区;真正把远程更新合并到当前分支的是merge这一步。
理解了这个底层逻辑,你就知道为什么有时候git pull会弹出一个提交信息编辑界面——因为它本质上是在执行一次合并,合并产生了新的提交节点。面对这个界面不用慌,按:wq然后回车(Vim 操作)即可保留默认提交信息。
5.3 多人协作的标准流程
日常协作中,一个比较安全的标准流程是:
git checkout maingit pull origin main,把主分支更新到最新git checkout -b feature/我的功能,从最新主分支拉出自己的开发分支- 开发完,commit 提交
git checkout main并git pull origin main,看看主分支有没有新动静git merge feature/我的功能,合并git push origin main
这套流程最核心的点在于:尽量减少长期分支存在的周期,每次合并前都先同步最新主分支。这么做不是为了好看,而是为了减少冲突面积——一个分支存活时间越久,它和主分支的差异就越大,合并时冲突点就可能越多。
6. 改错了怎么办:撤销与回滚的正确姿势
6.1 还没提交:checkout 与 restore
改完代码发现改砸了,想恢复到修改前的状态:
git checkout -- 文件名或者新写法:
git restore 文件名这个命令会用最近一次提交的版本覆盖工作区文件。警告:这个操作不可逆,所有未提交的改动会直接丢失,没有二次恢复机会。所以我通常建议先确认一下自己是不是真的不想要这些改动了,再执行。
如果改动已经执行过git add,想撤销暂存状态但不改文件内容:
git restore --staged 文件名这个操作只是把文件从暂存区“退回去”,代码内容还在工作区,所以相对安全。
6.2 已经提交了:reset 与 revert
已经 commit 了才发现有问题,有两个处理思路:git reset和git revert,很多人在这里容易搞混。
git reset本质是移动 HEAD 指针,相当于“回到过去”:
git reset --soft HEAD~1 # 撤销提交,保留改动在暂存区 git reset --mixed HEAD~1 # 撤销提交,保留改动在工作区(默认) git reset --hard HEAD~1 # 撤销提交,改动直接扔掉--soft和--mixed相对安全,真正的危险是--hard,它会从工作目录里直接删除改动,本地未推送的代码可能就此消失。
git revert则完全不同,它不做“穿越”,而是新生成一个反向提交,把某个旧提交造成的改动“抵消”掉:
git revert 某次提交的哈希值举一个实际场景:你昨天推送了一个提交,今天发现那个提交里有个 bug。由于远端代码可能已经被同事拉去用了,这时候用git reset --hard再强制推送是非常危险的,很可能导致同事本地历史错乱。而git revert只制造一个新的提交,不改变历史,所有人的仓库状态会自然平滑。
所以我的选择原则极其简单:
- 还没有推送的提交,想改就 reset
- 已经推送并且可能被其他人拉取的提交,用 revert
6.3 紧急场合:stash 临时保存现场
开发到一半需要切换分支,但又不想把当前半成品提交上去,这时候git stash就是你的救星:
git stash # 把所有未提交的改动暂存起来,工作区回到干净状态 git stash list # 查看暂存列表 git stash pop # 恢复最近一次暂存的改动并删除记录配合分支使用,这个命令能让你在不中断手头工作的情况下灵活切换上下文。老实说,这就是那种“用过的都说好、不用的人总觉得没必要”的功能,等你在现场救过几次火就明白它的价值了。
6.4.gitignore:让不该进仓库的文件别进来
这个虽然不属于撤销操作,但跟“避免错误”密切相关。像node_modules、target、build、dist、.env、IDE 配置文件这类文件,不应该提交进版本库。
做法是在项目根目录创建.gitignore文件,把要忽略的文件和目录写进去,例如:
node_modules/ dist/ .env *.log .DS_Store这个文件本身应该被提交到仓库里,这样所有协作者都共用同一套忽略规则。我见过不少人忘了配.gitignore,把node_modules整个提交进了仓库,结果仓库体积暴涨到几百兆,clone 一次慢到怀疑人生。
7. 避坑指南与常见问题排查
7.1 提交者身份不对
问题:commit 之后发现git log里的作者名和邮箱不对,或者远端显示“该提交不是本人”。
解决:修改配置后执行git commit --amend --reset-author,把当前提交的作者信息替换成新配置。这条命令同样适用于修改 commit 信息写错字的情况。
7.2 提交到了错误的分支
问题:在 main 分支上不小心提交了本该在 feature 分支上开发的代码。
解决:如果该提交还没推送,可以用git reset --soft HEAD~1把提交撤销到暂存区,然后切到正确的分支再git commit。如果已经推送了,就麻烦一些,需要小心处理远程历史,不过一般场景下尽早发现尽早改。
7.3 遇到大文件怎么办
问题:本想提交一个大资源文件,结果发现 push 的时候特别慢,或者远程仓库直接拒绝推送大文件。
解决:优先考虑项目是否真的需要这个文件入库。像视频、安装包、依赖库这类东西,更适合放在对象存储或各团队的制品仓库里,而不是塞进 Git 历史。一旦一个文件进入了历史,即使你后来删掉它,它依然留在 Git 的历史记录中,仓库体积减不下来。早期就配好.gitignore、严格审查入库内容,比后面想办法清洗历史容易得多。
7.4 中文文件名乱码
问题:含中文文件名的文件在git status和git log里显示成转义字符。
解决:Git 默认会对非 ASCII 文件名做转义显示,想恢复中文显示,执行:
git config --global core.quotepath false这条配置在许多社区中被广泛推荐,非常实用,至少我配置之后再也没被乱码困扰过。
7.5 误删了分支或丢失了提交
问题:删错了分支,或者 reset 之后发现刚才的提交里有些代码还想找回来。
解决:先用git reflog查看本地的全部操作记录,它记录了 HEAD 的一举一动。找到那次提交的哈希值后,有两种恢复路径:可以直接git branch 新分支名 哈希值,在指定哈希处重建分支;也可以git checkout 哈希值先查看内容再说。只要提交曾经存在,reflog 通常都能帮你找到它,所以我特别强调的是:Git 的绝大多数操作是可逆的,真的丢了什么东西,先别慌,还有救。
7.6 命令速查小抄
整理一份日常用的速查表,方便贴在手边:
| 场景 | 命令 |
|---|---|
| 查看状态 | git status |
| 查看简洁历史 | git log --oneline --graph --all |
| 查看工作区改动 | git diff |
| 暂存全部改动 | git add . |
| 提交 | git commit -m "说明" |
| 创建并切换分支 | git checkout -b 分支名 |
| 查看所有分支 | git branch -a |
| 合并分支 | git merge 分支名 |
| 推送分支 | git push -u origin 分支名 |
| 拉取更新 | git pull |
| 暂存现场 | git stash/git stash pop |
| 撤销未提交修改 | git restore 文件名 |
| 撤销最后提交并保留改动 | git reset --soft HEAD~1 |
| 安全撤销已推送提交 | git revert 哈希值 |
最后聊一点我自己的体会。Git 这个工具,刚用的时候觉得命令又多又绕,但等你在真实项目里摸爬滚打一阵子,你会发现它几乎不会“误伤”你,绝大部分“翻车”都来自对底层模型的不理解,或者操作太着急没看状态。我的建议一直是:动手之前先git status,commit 之前想清楚这次提交说明怎么写,合并之前先git pull。这三条看着简单,能长期做到,你的 Git 体验会比身边绝大多数人都顺。真要遇到拿不准的操作,在共享分支上优先想想还有没有其他同事会受影响,宁可保守也不要硬来。