渐进式 Git:从一张快照到多人协作
先会用,再理解模型,最后处理复杂情况。七课,每课一个能直接敲命令的沙盒,工作区、暂存区和提交图跟着你的命令实时变化。
你已经会 git add .,也会 git commit -m "..."。文件改了几天,一切顺利。直到一次 push 被拒绝,或者切分支时文件忽然变了,才发现:只记住那两条命令,还不足以解释眼前发生的事。
问题不在命令太多,而在于脑子里还没有一个能装下它们的模型。只要看懂 Git 里的三块区域(工作区、暂存区、提交历史)和两类便签(本地分支、远端书签),绝大多数报错和命令都会变成图上顺理成章的移动。
这份教程按「先会用 → 再理解模型 → 最后处理复杂情况」的路线拆成七课,每课配一个能直接敲命令的沙盒:画面上方实时展示工作区、暂存区和提交图,你每敲一条命令,它们就跟着变一次。沙盒是浏览器里的纯模拟,不会碰你电脑上的任何文件。
七课分三个台阶往上走:
- 第 1–3 课(立起本地模型):搞懂快照是怎么通过暂存区拍出来的,明白分支只是贴在提交上的便签,学会合并分叉与解决冲突。
- 第 4–5 课(连上远端协作):分清你手头的
main、GitHub 上的main和本地书签origin/main,学会在队友也推了代码时安全汇合。 - 第 6–7 课(救场与整理历史):按「改动停在哪一层」选对后悔药,再用
stash、rebase和cherry-pick把零散的修改整理成干净的历史。
读完你手里会有三样东西:一套每天都要用的命令循环、一张能解释分支和远程的图、一组处理麻烦事的急救手段。
第 1 课:给文件夹拍快照
Git 做的事,是把整个文件夹在某一刻的样子定格下来,存成一张快照,叫做提交(commit)。装着所有快照和历史记录的地方叫仓库(repository)——执行 git init,本质上就是在当前文件夹里建了一个隐藏的 .git 目录,用来放这些快照。
不过 Git 不希望你改一点就随手拍一张。它在你的手头文件和最终提交之间,多放了一层暂存区(staging area,也叫 index):
工作区(你正在改的文件) ── git add ──▶ 暂存区(下一次快照的取景框) ── git commit ──▶ 提交历史(拍好的快照)
- 工作区:你在编辑器里直接看到、随便涂改的草稿桌。
- 暂存区:下一次提交的「挑拣台」。
git add就是把挑好的改动放上台面,决定下一张快照里装什么。 - 提交历史:
git commit按下快门,把暂存区此刻的样子永久封存成一张快照,贴上独一无二的哈希指纹(如a1b2c3d),并记下它是接在哪个旧快照后面的。
多这半步的意义很实在:当你顺手改了五个文件、修了两个无关的问题时,不用一股脑全塞进一次提交,而是可以先 git add 其中两个文件,拍成一笔干净的快照,剩下的单算一笔。
这一课的核心是分清工作区、暂存区和提交,并养成每天写代码的基本循环:
git status:看一眼现在哪些文件改了、哪些已经放进了暂存区、哪些还是没登记的新文件(Untracked)。git diff:核对具体改了哪几行。git add <文件>:把确认要留下的改动放进暂存区。git commit -m "说明":拍下快照,写一句清楚的说明。git log:翻看已经拍下来的快照历史。
注意沙盒里的一个小细节:你会先对文件执行 git add,然后立刻往同一份文件里再追加一行。这时你会发现,暂存区记住的是 add 那一刻的文件快照,不会跟着工作区的原稿自动更新。这也是为什么查看改动有两条命令:
git diff:比较工作区 vs 暂存区——看你add之后,手头又多改了什么(还没暂存的部分)。git diff --staged:比较暂存区 vs 最近一次提交——看如果现在按下git commit,下一张快照到底会装进什么。
这两条对比命令配合 git status,就能把工作区和暂存区的落差看得一清二楚。git status 和 git diff 都是只读的,任何时候都可以放心敲;不知道该干什么的时候,先看一眼总是划算的。
第 2 课:分支是一张会移动的便签
每拍下一张新提交,它都会在身体里记上一笔:「我的父提交(上一张快照)是谁」。因此,提交历史不是零散的照片,而是一条后一个指向前一个的链条,画出来就是一张提交图。
很多人以为建分支是把整个文件夹复制一份,其实完全不是:
- 分支(branch):只是贴在某个提交上的一张轻量便签,里面只写着那个提交的哈希值。建仓库时默认给你的那张便签叫
main。 - HEAD:是代表「你此刻正站在哪张便签上」的指示牌。
看懂了便签和指示牌,平时最常用的两个动作就一点也不神秘了:
- 当你
git commit时:Git 拍下新快照,让它指向当前提交,然后HEAD带着它脚下的那张分支便签一起往前挪一格,别的分支便签留在原地不动。 - 当你用
git switch切换分支时:HEAD跳到目标分支那张便签上,同时 Git 把你的工作区和暂存区替换成目标快照的样子。
所以同一个文件在不同分支上可以呈现出不同的内容——在下面的沙盒里,你会在 draft 分支写下「第三章」并提交;因为 main 便签还停在旧提交上,第三章只在 draft 的快照里,不在 main 的快照里。
切回 main 的时候,第三章会消失吗?
那怎么把 draft 上的成果合回 main?如果在你写 draft 的这段时间里,main 根本没动过(也就是说,main 依然是 draft 的直接祖先,两条路没有分叉),那么站在 main 上执行 git merge draft 时,Git 甚至不需要拍新快照——它只要顺着链条,把 main 这张便签直接滑到 draft 所在的提交上就够了。这种顺着原路追上去的合并,叫做快进(fast-forward)。
一句话概括这一课:分支是便签,不是副本。
git switch -c <分支名>:在当前提交上贴一张新便签,并让HEAD站过去。git switch <分支名>:让HEAD换个便签站,工作区跟着换成对应快照。git merge <分支名>:把目标分支的历史合并到当前分支。git branch -d <分支名>:撕掉那张已经用完的便签。删除一个已经合并的分支,不会删掉任何提交,只是取下便签而已。
切换前,桌上还有没收好的东西
这课先把修改提交好再切换。真实 Git 有时会让未提交的修改跟着走,有时会因为目标分支可能覆盖它们而拒绝。遇到拒绝,先看 status、保存手头工作,不必强行切换。
main 也不是每个仓库固定的名字,以你看到的分支名为准。HEAD 还能直接指向某个提交;等确实遇到这种情况,再认识 detached HEAD 也不迟。
第 3 课:冲突是 Git 不敢替你做的决定
第 2 课之所以能「快进」,是因为 main 站在原地等 draft。但如果从同一个起点出发后,两条分支各自都有了新提交,提交图真正分叉成了两条路,只挪便签就不管用了。
这时执行 git merge,Git 会做一次三方合并(three-way merge):同时拿出三张快照来对比——双方的共同祖先、当前分支(HEAD)、以及要合进来的目标分支。
- 如果某一段只有一边改了,另一边还保持着共同祖先的原样,Git 会自动把改过的那一版收进来;
- 只有当两个人(或者两个分支上的你)改了同一个文件的同一处,而且改出来的内容不一样时,Git 才没法替你拍板,只能暂停合并,把选择权交给你。
所以冲突(conflict)不是故障,更不是你操作错了——它是 Git 在保护你的代码,请你来做最后决定。
发生冲突时,打开那个文件,你会看到 Git 插入的三行分界标记:
<<<<<<< HEAD
这里是你当前所在分支(HEAD)写的内容
=======
这里是正要合并进来的那条分支写的内容
>>>>>>> feature
解决冲突不需要什么特殊命令,就是打开编辑器做选择题:你可以留上面那版、留下面那版,或者把两版融合成更完整的新写法——唯一的要求是把 <<<<<<<、=======、>>>>>>> 这三行标记彻底删干净,让文件变成你最终想要的样子。
沙盒里的合并收尾不用手写说明,Git 会自动生成一句默认的合并提交说明;在真实终端里执行 git commit 时,通常会先弹出编辑器让你确认这句说明,保存退出即完成合并。
注意观察合并完成后的提交图:这笔新的合并提交(merge commit)伸出了两条箭头,分别指向合并前的两个分支顶端,在图上收束成一个「Y」字。
记住解决冲突的固定三步:改文件(删标记、定内容) → git add <文件> → git commit。这里的 git add 就是把你裁决好的最终版本放进暂存区,相当于正式通知 Git:「这处冲突我已经处理完了」。Git 只看你有没有 add,不会替你检查文件里是不是还残留着 <<<<<<< 标记,所以 add 之前最好用 git diff 看一眼。
冲突太多,不想解了
git merge --abort 会把工作区恢复成合并开始前的样子,连已经 add 过的解决结果也会一起回退。开始前如果混着未提交修改,它不一定能完整还原现场,所以从干净工作区开始合并更稳妥。
第 4 课:克隆、提交、推送
前 3 课里,所有操作都发生在你自己的电脑上。到了多人协作,我们需要一台大家都连得上的服务器(比如 GitHub 或 GitLab)来放共享仓库。在 Git 的模型里,服务器上的仓库和你电脑里的仓库结构一模一样,只是大家约定以它为同步中心。
当你拿到一个项目地址、执行 git clone <地址> 时,Git 一口气替你办好了三件事:
- 把远端仓库里的所有提交快照完整复制一份到你本地;
- 把那个服务器地址记成一个简短的代号,默认叫
origin; - 在你本地建好默认分支
main,并让它绑定跟踪远端的同名分支(这个绑定对象叫做上游 upstream)。
这里有一条最容易让人绕晕、但看懂后立刻豁然开朗的概念:你在提交图上看到的 origin/main,并不是网络另一头实时的那个 main,而是存在你电脑里的一张「本地书签」——它记录的是「你上一次和 GitHub 联网通话时,对方的 main 停在哪个提交」。
不妨把这三张便签放在一起对比:
- 本地的
main:你手头正在用的分支便签。你在本地每git commit一次,它就往前走一格。 - GitHub 上的
main:远端服务器里的分支便签。只有有人成功push时,它才会动。 - 本地的
origin/main:你电脑里的只读路标,用来记 GitHub 上次的位置。你在本地commit时它不动;只有你联网执行push、fetch或pull时,它才会更新。
盯着沙盒左右两栏的变化,你会看清日常推送的两拍节奏:
- 本地
git commit:左栏里你自己的main往前走了一格,而origin/main留在原地。这时git status会告诉你:Your branch is ahead of 'origin/main' by 1 commit(你的本地分支比记忆中的远端领先 1 笔提交)。 - 执行
git push:Git 把你多出来的那笔提交上传给右栏的 GitHub,让 GitHub 的main往前挪一格;确认成功后,再把你左栏里的origin/main书签也挪上来,三张便签重新对齐。
git clone、git push、git pull 是日常出现频率最高的远程命令。如果你在本地新建了一条分支(比如 feature),因为 GitHub 上还没有它,第一次推送时要用 git push -u origin feature——其中的 -u(--set-upstream)会把本地 feature 和远端的 feature 绑定起来,以后在这条分支上只要直接敲 git push 就行。
离开沙盒,第一次连接 GitHub
真实连接还需要配置 HTTPS 或 SSH 认证。不要把 token 写进笔记或提交;沙盒不需要任何真实凭据。
git remote -v 看地址,git branch -vv 看跟踪关系。提交成功、推送成功和网站部署成功,是三件不同的事。
第 5 课:别人也在推
单人用远程仓库很少出岔子;协作真正的日常是:你克隆或拉取之后,在你写代码的这段时间里,队友已经先往 GitHub 推了新提交。
这时你本地的 origin/main 书签还停留在旧位置,但 GitHub 上的 main 已经往前走了。等你写完自己的提交、敲下 git push 时,就会收到一条 rejected (non-fast-forward) 报错。
推送被拒绝不是坏事——它是 GitHub 替团队守住的闸门:你的新提交是从旧起点长出来的,跟队友刚推上去的提交已经分叉了;如果允许你硬把远端 main 拽过来,队友的提交就会被甩出历史链条。(当然,认证失败、网络断开或分支保护规则也会拒绝 push,动手前先读一眼报错信息,别一律当成历史分叉。)
遇到远端有更新,先分清两个「往下拿」的命令:
git fetch(只看不动):连上 GitHub,把队友新推的提交下载到你本地仓库,并把origin/main书签更新到最新位置——但它绝对不碰你的工作区文件,也不动你当前的本地main分支。敲完fetch看一眼提交图,你的main和origin/main一左一右分叉的样子就一目了然了。git pull(拿回来并整合):相当于先执行一次git fetch,紧接着立刻把origin/main整合进你当前的本地分支。
既然两条线已经分叉,怎么整合?Git 提供了三种策略(由于不同 Git 版本和本地配置对裸 git pull 的默认行为不同,显式写出参数最稳妥):
git pull --ff-only:只接受快进合并;一旦发现历史分叉就立刻停下报错,由你自己决定下一步。git pull --no-rebase:用第 3 课的普通合并(merge)把两边接起来,多生成一个双父节点的合并提交。git pull --rebase:变基整合——先拿远端的新提交垫底,再把你本地还没推过的提交「拎起来」,逐个重放到origin/main后面。
本课的沙盒采用最后一种 git pull --rebase。你会看到:你本地那笔尚未共享的提交被挪到了队友提交的后面,分叉重新拉直成了一条线。因为这笔提交换了一个新的父提交,它相当于被重新拍了一次快照,哈希值也换成了新的(改动的上下文变了,真实开发中搬完家后应当重新跑一遍测试)。既然你的 main 重新成了 origin/main 的直接后代,这时再敲 git push,就能顺畅通过了。
pull —rebase 之外的选择
分叉时的三种收法:
git pull --no-rebase:产生一个合并提交,历史先分叉、再合拢。git pull --ff-only:只允许快进,一旦分叉就报错,逼你显式处理。git pull --rebase:获取后重放本地提交,适合还没有别人依赖的个人工作。
没有绝对正确的选项;团队用哪种,就跟着用哪种。rebase 遇到冲突时,改文件、git add,再 git rebase --continue;要取消用 git rebase --abort。它和普通 merge 的收尾命令不一样。
第 6 课:后悔药
在 Git 里撤销操作,最怕不管三七二十一乱搜一条命令贴进去。其实只要先问自己一句 「我想反悔的东西,现在走到哪一层了?」,答案就自动对号入座了:
- 第 1 层:还在工作区(改乱了文件,还没
add) 用git restore <文件>丢弃工作区里还没暂存的修改。没额外指定来源时,它会用暂存区里的版本覆盖工作区;如果暂存区没放新东西,就等于恢复成最近一次提交的样子。 - 第 2 层:已经进了暂存区(手滑
add了不该交的文件,还没commit) 用git restore --staged <文件>把它从暂存区撤出来。别担心,你在工作区文件里写的字一个都不会少,它只是退回了「未暂存」状态。 - 第 3 层:刚
commit完,想补交文件或改说明(且还没push) 用git commit --amend修补最后一次提交。它会把当前暂存区里的内容并进去,生成一笔新提交替掉刚才那一笔。 - 第 4 层:在本地连交了好几笔,想退回前面的某个提交(且还没
push) 用git reset <目标提交>,直接把当前分支便签往回挪到目标提交上。 - 第 5 层:错误的提交已经
push到了共享分支 不能再改写历史了,改用git revert <提交>。它不会抹掉旧提交,而是新拍一笔完全相反的提交(比如旧提交加了三行,它就删掉那三行)来抵消错误,历史继续往前走,谁的仓库都不会乱。 - 第 6 层:手滑
reset过了头,提交在git log里找不到了 用git reflog翻看 HEAD 的行动日记。找到落单提交的哈希值后,先用git show <哈希>核对内容,再用git branch rescue <哈希>往它身上贴一张新便签,那笔提交就稳稳找回来了,不必慌张地乱敲reset --hard。
其中第 4 层的 git reset 在挪动分支便签时,有三种力度可选。所谓「对齐暂存区 / 工作区」,不是把文件删空,而是让它们退回到目标提交那一刻的内容:
| 模式 | 分支便签(HEAD) | 暂存区 | 工作区文件 | 撤销下来的改动去哪了? |
|---|---|---|---|---|
git reset --soft <目标> | 退回目标提交 | 保留不动 | 保留不动 | 全都留在暂存区(已 add 状态),适合把最近几笔零碎提交合并重拍成一笔 |
git reset --mixed <目标>(默认) | 退回目标提交 | 退回目标提交 | 保留不动 | 全都留在工作区(未 add 状态),适合重新挑拣哪些要暂存 |
git reset --hard <目标> | 退回目标提交 | 退回目标提交 | 退回目标提交 | 已跟踪的修改被直接抹除,彻底回到过去那一刻(还可能清掉挡路的未跟踪文件) |
为什么 reset --hard 退回去之后,git log 里看不见刚才的提交,而 git reflog 却能看见?因为 git log 只顺着当前分支便签的「父提交链条」往回溯源,被甩在链条外面的提交它不会显示;但 Git 并不会立刻销毁那张快照,reflog 在本地按时间顺序记下了 HEAD 每一次移动的脚印(commit、switch、reset、rebase 全在账上),所以顺着脚印就能把旧快照捞回来。常见默认设置中,一般记录过期时间为 90 天,不可达记录为 30 天;配置和对象清理也会影响能否恢复。它不是长期备份,未提交的草稿也不在里面。
第 7 课:把历史捋直
学会了救场,最后一课给你三件整理手头工作与提交历史的工具。它们解决的是开发中最常碰到的三种打断与穿插:
-
手头改到一半,突然要切去别的分支处理急事 ——
git stash(临时抽屉) 代码写了一半还跑不通,既不想凑合拍一笔半成品提交,又需要干净的工作区切分支。这时敲git stash,Git 会把当前工作区和暂存区里所有已跟踪的改动打包收进一个临时抽屉,让工作区瞬间恢复成最近一次提交的干净样子;等你忙完回来,敲git stash pop,改动就会原样放回桌面上。 留心:新建但从未git add过的未跟踪文件(Untracked)默认不会进抽屉,需要带上它们时用git stash -u。 -
在自己的分支写了几天,想把地基挪到最新的主线上 ——
git rebase <目标分支>(变基) 你从几天前的main切出一条分支写功能,期间main上又推进了几笔新提交。站在你的分支上执行git rebase main,Git 会把你这条分支独有的提交依次「拎起来」,以最新的main顶端为新起点,按顺序一笔一笔重放(replay)上去。 留心:变基之后,分叉的树枝变成了笔直的一条线,但重放出来的每一笔提交都是重新拍的快照,哈希值全部变了;同时因为垫底的代码变了,即使没报文本冲突,合并后的代码逻辑也值得重新验证一遍。 -
别的分支上有一笔紧急修复,只想把那一笔摘过来 ——
git cherry-pick <提交哈希>(摘樱桃) 队友的分支上有十几个开发中的提交,你还不想把整条分支合进来,但急需其中修 Bug 的那一笔。站在你自己的分支上执行git cherry-pick <哈希>,Git 会单独提取那笔提交「改了什么」,在当前分支上照样重做一遍,生成一笔内容相同、哈希不同的新提交。 留心:如果那一笔修复依赖它前面的其他提交,单摘这一颗可能会发生冲突,或者摘过来也跑不通。
把这三件事放在一起看,核心前提只有一个:你清楚自己是在暂存草稿,还是在改写提交历史。比如 rebase 之后,旧提交虽然还躺在本地的 reflog 里,但分支便签已经指向了哈希完全不同的一串新提交——如果别人已经拿走了旧版本,你们俩的历史就对不上了。
变基还是合并,怎么选
只有自己在用、想让历史保持直线:rebase。多人共享的分支、或者想保留「这里发生过一次汇合」的记录:merge。团队可以约定在个人分支变基、在共享主线合并;关键是说清哪些历史允许改写,哪些需要保留。
stash pop 成功后会删掉那条记录;冲突时通常会留下。想更稳,可以先 git stash apply,检查后再 git stash drop。
一张总表
| 场景 | 命令 | 说明 |
|---|---|---|
| 开始一个仓库 | git init | 把当前文件夹变成仓库(创建 .git 目录) |
| 看状态 | git status | 不知道干什么时,先敲它 |
| 看还没暂存的改动 | git diff | 对比工作区与暂存区(add 之后又改了什么) |
| 看下一次提交的内容 | git diff --staged | 对比暂存区与上次提交(提交前最后确认一遍) |
| 挑内容进暂存区 | git add <文件> | git add . 含当前目录的删除;-A 覆盖整个仓库 |
| 拍快照 | git commit -m "说明" | 说明写「为什么改 / 做了什么」,不只是「改了哪个文件」 |
| 看历史图 | git log --oneline --graph --all | 历史是一张图,把所有分支放在一起看最清楚 |
| 开分支 / 切换 | git switch -c <名字> / git switch <名字> | 旧写法是 checkout -b / checkout |
| 合并分支 | git merge <分支> | 视分叉情况执行快进或生成双父节点的合并提交 |
| 解决冲突 | 改文件(删标记) → git add → git commit | 中途想反悔用 git merge --abort |
| 克隆远端仓库 | git clone <地址> | 复制全部提交 + 记住 origin + 设好默认分支上游 |
| 推送 / 拉取 | git push / git pull --rebase | 新建的本地分支第一次推送用 git push -u origin <分支> |
| 只拿远端更新 | git fetch | 下载新提交并更新 origin/main 书签,不碰工作区 |
| 丢弃工作区改动 | git restore <文件> | 默认从暂存区恢复,会直接覆盖未暂存的草稿 |
| 撤出暂存区 | git restore --staged <文件> | 把文件从暂存区拿出来,工作区改动完好保留 |
| 改写最后一次提交 | git commit --amend -m "…" | 补交暂存区内容或改说明;别对已推送的提交用 |
| 回退本地历史 | git reset --soft/--mixed/--hard <位置> | 只挪便签 / 连暂存区退回 / 连工作区一起抹除 |
| 撤销已推送的提交 | git revert <提交> | 新增一笔反向提交抵消改动,不改写已有历史 |
| 找回丢失的提交 | git reflog → git show → git branch rescue | 查 HEAD 脚印 → 核对快照内容 → 贴便签救回 |
| 暂存半成品 | git stash / git stash pop | 抽屉收放已跟踪修改;包含未跟踪新文件加 -u |
| 换个起点重放提交 | git rebase <目标> | 把本地提交挪到新基底重放,别动已共享的分支 |
| 摘取单个提交 | git cherry-pick <提交> | 把别的分支上某一次提交的改动在当前分支重做一遍 |
这张表不用背。用的时候回来查,或者在自己的仓库里敲一遍。git add . 很省事,但刚开始用时写明文件名,更容易看清自己到底往取景框里放了什么。至于密码密钥、编译生成的临时产物和体积巨大的数据文件,用 .gitignore 把它们挡在快照外面。
离开沙盒前,试着解释三个结果
合上命令表,先在脑子里用「工作区、暂存区、提交图与便签」预测结果,再用前面的沙盒验证:
-
对同一个文件先执行了
git add,紧接着又改了几行,这时直接git commit会保存哪一版?模型核对:保存的是执行
git add那一刻的版本,后改的那几行不会进这次提交。因为git commit只对暂存区拍快照,不管工作区后来又涂改了什么;后改的那几行仍留在工作区里,需要再git add一次才会进入下一次提交。 -
队友推了新代码,你执行
git fetch之后,origin/main比main更新了,你手头的工作区文件会立刻改变吗?模型核对:不会改变。
git fetch只把远端的新提交下载到本地仓库,并把origin/main这张本地书签往前挪;你当前站着的HEAD和本地main便签还在原位,工作区文件自然纹丝不动。只有接着执行merge或rebase(或直接git pull)时,工作区才会更新。 -
一笔提交已经
push给了队友,发现写错了需要撤销,为什么更适合用git revert留下反向提交,而不是git reset?模型核对:
git reset是把分支便签往回拽,把那笔提交从历史链条上甩出去;一旦队友已经基于那笔旧提交继续往下写了,两边历史就会分叉错位,或者队友一push又把旧提交推了回来。而git revert是在当前顶端往前新拍一笔抵消改动的提交,旧历史完全不动,队友只需要正常pull就能同步这次撤销。
只要你的解释里自然出现了工作区、暂存区、提交图和便签,你就已经开始用模型决定动作,而不只是死记命令了。哪一题卡住了也没关系:回到对应那一课的沙盒,亲手敲一遍,盯住上方面板的变化就能对上号。
接下来
沙盒里的 Git 是照着真实 Git 的输出和行为写的,但它毕竟只有一层文件夹、一小撮命令。真正的训练是:在你电脑上建一个练习目录,git init,把这七课的组合拳亲手打一遍;然后回到你的真实项目里,从敲一声 git status 开始。
真实终端的起步准备
示例面向 Git Bash 或 Linux / macOS shell,建议 Git 2.28 及以上,这样 git init -b main 才可用。目录名已经存在就换一个:
mkdir git-play
cd git-play
git init -b main
git config user.name "Practice User"
git config user.email "practice@example.com"这两条身份配置只作用于这个练习仓库,不是 GitHub 登录。接着用编辑器写 note.txt,执行 git add note.txt 和 git commit -m "写下第一句"。改第二句之前先 git diff。沙盒里的 edit 文件 是辅助命令,真实终端用你自己的编辑器。
遇到具体的撤销问题,再查 restore、reset 和 reflog 的说明。
远程整合策略以 pull 手册 和本机 git help pull 为准;这次文档核对日期为 2026-10-02。
祝你和 Git 的相处,从「不敢动」变成「随时可以重来」。