10 分钟学会 Git

大多数 Git 教程一上来就教命令。你在不对的状态下敲错一次,Git 吐出一整段吓人的文字,于是你把项目复制到新文件夹里,从头再来。这一页先讲清模型:三棵树,还有像便利贴一样的分支。模型懂了,命令自然明白,那些“噩梦”也就没什么可怕的。

🎙️ 发布并录制于: · 更新于 ·

01心智模型:三棵树,存快照而非差异

每条 Git 命令,都是在三个地方之间移动文件。第一个是工作目录,也就是你正在编辑的文件。第二个是暂存区,可以把它看成下一张快照的装货台。第三个是仓库,也就是永久保存的相册。还要记住:一次提交保存的是项目的完整快照,不是差异。弄懂这两点,Git 一半的怪脾气都会消失。

# 三棵树,从左到右:
#  working dir  --add-->  staging  --commit-->  repository

git init                # 开始跟踪这个文件夹
git status              # 现在所有内容分别在哪里?
你一定会遇到
fatal: not a git repository (or any of the parent directories): .git
你进错文件夹了。Git 会从当前目录开始向上查找隐藏的 .git 目录。用 cd 进入真正的项目目录,也就是你运行过 git initgit clone 的文件夹。新手最常在刚打开终端时遇到它,因为终端默认停在用户主目录。

02日常循环:status → add → commit

使用 Git 时,百分之九十的操作都在重复三个步骤。请频繁运行 git status,最好每条命令前后都看一眼。它不花钱,也不费时间,还会明确告诉你改动正在哪棵树里。资深开发者也会不停敲这条命令,没什么不好意思的。

git status                     # 1. 哪些内容变了?
git add main.py                # 2. 把文件放上装货台
git add .                      #    (或者暂存当前目录下的全部内容)
git commit -m "Fix login bug"  # 3. 拍下快照

git log --oneline              # 查看相册,每张快照显示一行
你一定会遇到
Changes not staged for commit
你在添加文件之后又改了它,接着直接提交,所以最新改动没有进这次提交。add 只复制文件在那个时刻的样子。再次编辑,就要再次添加。这是“Git 吃掉了我的改动”最常见的错觉。改动还在工作目录里,只是没有进入提交。

03分支是便利贴,不是文件夹

分支不是代码的副本。它只是一张指向某次提交的便利贴,总共四十一个字节。每次提交时,你当前所在的便利贴会向前移动。这就是创建分支为什么一瞬间就能完成,也说明你完全可以放心地多建分支。头指针,也就是 HEAD,只表示“你现在站在哪张便利贴上”。

git switch -c try-new-parser   # 写一张便利贴,然后站上去
# ……放心修改,照常提交……

git switch main                # 跳回主分支,磁盘上的文件会变化
git merge try-new-parser       # 把实验成果合并进 main
git branch -d try-new-parser   # 扔掉便利贴,提交仍然保留
说句实话
老教程让你凡事都用 git checkout。别照做。checkout 把三件互不相干的事塞进了同一个名字,也很容易让人误入 detached HEAD,也就是第 07 节的分离头指针。新版 Git 已经拆开了它:切换分支用 switch,还原文件用 restore。记住这两个词,你可能一年也用不到一次 checkout

04推送与拉取:你的仓库有个双胞胎

GitHub 上保存着仓库的完整副本,通常叫作 originpush 把你的新快照送上去,pull 把队友的快照拉下来。就这么简单。下面那个著名的拒绝报错,人人第一次见都会紧张。解决它只需要养成一个习惯:先拉取,再推送。

git clone https://github.com/user/repo.git   # 复制仓库并连接 origin
git pull                       # 开工前取得最新内容
git push                       # 发布你的提交

git push -u origin my-branch   # 第一次推送新分支
你一定会遇到
! [rejected] main -> main (non-fast-forward)
error: failed to push some refs

仓库没有坏。Git 只是拒绝覆盖队友的工作。你工作期间有人推送了新内容。运行 git pull,把对方的快照合并到自己的快照里;如有冲突就解决,然后重新推送。无论如何,别在共享分支上使用 --force。那会让队友一个下午的工作凭空消失。

05撤销任何操作:救命表

“怎样在 Git 里撤销某个操作”,大概是软件开发中搜索次数最多的问题。完整答案就在下面,按你想撤销什么来查。先记一条规矩:如果提交已经推送,就用 revert。它会新增一次反向提交。不要用 reset,因为它会从队友脚下改写历史。

# 取消暂存文件,但保留改动
git restore --staged secret.txt

# 丢弃我对单个文件的改动,小心,真的无法恢复
git restore oops.py

# 重写上一次提交的说明
git commit --amend -m "Better message"

# 撤销上一次提交,但保留工作成果,这是最常见的需求
git reset --soft HEAD~1

# 撤销已经推送的提交,这是安全的公开撤销方式
git revert abc1234
能救职业生涯的一招
reset --hard 删除了一次提交,又想找回来?git reflog 会列出 HEAD 到过的所有位置,包括所谓“已删除”的位置。找到事故发生前那一行,再运行 git reset --hard HEAD@{2}。Git 几乎不会立即真正删除提交。通常要放着不管约 30 天,垃圾回收才会处理它。大多数时候,慌乱都有补救办法。

06合并冲突:它只是一个文本文件

冲突不是报错。Git 只是在说:“两个人改了同一批行,我不替你们猜。”接着,它会把两个版本都写进文件,加上标记,然后等你处理。你只需打开文件,留下真正需要的内容,删掉标记,再提交。全部流程就是这样。

CONFLICT (content): Merge conflict in config.py

# 打开 config.py 会看到:
<<<<<<< HEAD
timeout = 30          # 你的版本
=======
timeout = 60          # 对方的版本
>>>>>>> feature-x

# 按真实需求修改,也许是 timeout = 60,也许是别的值,
# 删除全部三行标记,然后运行:
git add config.py
git commit
人们常错在这里
把标记也提交了。如果运行中的代码里出现 <<<<<<<,说明你合并后没有完成编辑。没错,应用会因语法错误而崩溃,报错位置正好指向那一排尖括号。冲突处理到一半想退出,可以运行 git merge --abort,一切会恢复原状。知道紧急出口一直都在,冲突也就没那么吓人。

07分离头指针:不是报错,只是一次参观

总有一天,你会切到一笔旧提交去查看内容,Git 随即打印出软件世界里最吓人的一段话。翻译一下就是:你现在直接站在某次提交上,而不是站在分支这张便利贴上。只看不改完全安全。唯一的风险,是在这里提交新工作。它不属于任何分支,所以很容易弄丢。

git switch --detach abc1234    # 回到过去,查看一张旧快照

You are in 'detached HEAD' state. You can look around, make
experimental changes and commit them, and you can discard any
commits you make in this state without impacting any branches...

git switch -                   # 查看完毕,回到原来的位置

# 在这里做了想要保留的提交?
git switch -c rescued-work     # 贴上一张便利贴,保存成功

08.gitignore,以及误交密钥的那一天

名为 .gitignore 的文件,列出了 Git 应该假装不存在的东西,比如依赖、构建产物,以及装着密钥的 .env 文件。每个项目开始后的前十分钟就写好它,因为这里有个坑:它只对 Git 尚未跟踪的文件生效。

# .gitignore
node_modules/
.env
*.log
__pycache__/

# “我把 .env 加进 .gitignore 了,Git 怎么还在跟踪它?”
# 它以前已经提交过。停止跟踪,但保留本地文件:
git rm --cached .env
git commit -m "Stop tracking .env"
倒霉的一天
把 API 密钥推送到 GitHub 了?在新提交里删除文件毫无作用。密钥仍留在历史中,而爬虫几分钟内就会扫描公开仓库。正确顺序是:1)立刻去服务商那里吊销密钥;2)如果这个仓库很重要,再用 git filter-repo 清理历史。轮换密钥才是真正的修复;修改历史只是在做清洁。

09速查表

这些就是人人都会搜索的命令。按照你脑子里正在问的那句话来查。

# “所有内容现在是什么状态?”
git status

# “保存我的工作”
git add .  &&  git commit -m "message"

# “撤销上一次提交,但保留工作成果”
git reset --soft HEAD~1

# “这次已经推送的提交错了”
git revert <hash>

# “丢弃我对这个文件的修改”
git restore <file>

# “我的推送被拒绝了”
git pull   # 然后重新推送

# “这次合并乱成一团,赶紧退出”
git merge --abort

# “我删掉了重要内容”
git reflog   # 找到它,然后运行:git reset --hard HEAD@{n}

# “先收起乱摊子,我需要五分钟干净的工作区”
git stash    # 稍后运行:git stash pop

这就是你的 Git 生存包。Git 更深层的真相是:它几乎从不弄丢你的工作,只是把东西归到了一个你还不知道该去哪里找的位置。这一页里的每条吓人消息,都有两行以内的退出办法。现在你全都知道了。

Tell me what missed

A correction is more useful than a compliment. This goes straight to the person who writes SwiftGrasp.

Was this page useful?
0/1000

Please do not include passwords, private keys, or personal information.