Git 入门学习笔记
2026-05-05 第一次完整学 Git,记录全程知识点和踩坑经历。
一、Git 到底是什么
Git 是一个版本管理工具,核心就是给文件夹打"快照":
1 | |
- "快照"就是某时刻你所有文件的状态
- 打一次快照 = 一个 version
- 以后可以随时回到任意一个旧版本
二、第一步:初始化仓库
1 | |
执行完之后,这个文件夹里多了一个 .git
隐藏文件夹。这就是 Git 的"小本本",里面记着所有版本信息。
💡 不要删
.git文件夹,删了就什么都没了。
三、配置身份(为什么需要?)
你遇到的报错:
1 | |
Git 不认识你是谁。每次 git commit
都会在记录里写下是谁提交的,像这样:
1 | |
如果不配置,Git 就不知道写谁的名字。
解决方法:
1 | |
--global 的意思是这台电脑上所有 Git
项目都用这个配置,只配一次就行。如果去掉
--global(git config --local),那就只对当前项目生效。
💡 如果用 GitHub,邮箱要和 GitHub 注册邮箱一致,这样 GitHub 能正常显示你的头像和提交记录。
四、.gitignore —— 排除不该提交的文件
为什么需要它
git add .
会把所有文件都加到暂存区。但有些文件不应该被 Git
管理:
| 文件 | 为什么不该提交 |
|---|---|
node_modules/ |
里面有成百上千个包,又大又没用。别人拉代码后
npm install 就有 |
tmp_* |
临时测试文件,只在你电脑上有意义 |
*.log |
日志文件 |
.DS_Store |
Mac 系统的缓存文件 |
第一个大坑:Notepad 创建
.gitignore
1 | |
结果: Notepad 给文件名加了 .txt
后缀,变成了 .gitignore.txt。但你检查的时候只看得到
.gitignore(Windows 默认隐藏已知扩展名)。
所以虽然你写了内容,但实际文件是 .gitignore.txt,Git
压根没读取它。
💡 正确做法:直接用 PowerShell 创建,不要用 Notepad。
第二个大坑:PowerShell 的
> 编码问题
1 | |
PowerShell 的 >(其实是 Out-File
的别名)默认输出 UTF-16 LE with BOM 编码。但 Git 只认
UTF-8 without BOM。所以 Git 虽然读到了文件名
.gitignore,但看不懂里面的内容——等于没写。
要记得,Windows 里常见的"文本文件"可能有好几种编码:
| 编码 | Git 能否识别 |
|---|---|
| UTF-8 without BOM | ✅ 能 |
| UTF-8 with BOM | ⚠️ 部分场景会翻车 |
| UTF-16 LE with BOM | ❌ 不能,PS 默认输出这个 |
| ANSI/GBK | ⚠️ 含中文可以,但不推荐 |
正确的创建方法
1 | |
检测它是否生效
1 | |
这段命令的意思是"检查 node_modules 里的某个文件是否被忽略规则匹配到了":
- 有输出 比如
.gitignore:1:node_modules/ node_modules/xxx→ ✅ 生效了 - 没输出 → ❌ 没生效,要么文件名不对、要么编码不对、要么路径不对
第三个大坑:文件已经被跟踪了,.gitignore 管不了
这是新手最容易晕的地方。.gitignore 的规则是:
只对「还没有被 Git 跟踪」的文件生效。
如果你先 git add . 把 node_modules/
加进去了,然后再创建 .gitignore 写上
node_modules/,但这时 node_modules/ 已经被 Git
记住(跟踪)了,.gitignore 拦不住它。
你需要先把它们从 Git 的"花名册"里移除:
1 | |
这条命令是什么意思:
| 部分 | 含义 |
|---|---|
git rm |
移除文件 |
-r |
递归(recursive),包括子文件夹 |
--cached |
只从 Git 的缓存/跟踪列表里删,不删你硬盘上的原文件 |
. |
所有文件 |
💡 这个
.和git add .里的.是同一个意思,表示"当前目录"
但你可能遇到这个报错:
1 | |
为什么: .gitignore
这个文件本身在暂存区里有一份(之前 git add
过的),而你后来又修改了它(编辑内容),所以 Git
检测到"暂存区里的版本"和"硬盘上的版本"不一样,为了安全拒绝直接移除。
解决: 加 -f(force)
1 | |
然后用 .gitignore 过滤好的内容重新加入:
1 | |
此时 node_modules/ 和 tmp_*
应该都不在里面了(用 git status 确认)
巨坑:core.excludesFile 干扰
为了解决 .gitignore
不生效的问题,我教你设了一个配置:
1 | |
本意是"显式告诉 Git 从哪个文件读忽略规则"。但设了这个之后,反而让 Git
的行为变得奇怪——它可能覆盖了默认的 .gitignore
查找逻辑。最后用 --unset 删掉就好了:
1 | |
教训: .gitignore
不需要任何额外配置。把它放在项目根目录,Git
会自动读取。不要画蛇添足。
五、.gitattributes —— 换行符问题
为什么 Git 会烦你
Windows 和 Linux/Mac 用不同的换行符:
| 系统 | 换行符 | 说明 |
|---|---|---|
| Windows | CRLF(\r\n) |
回车+换行,占两个字符 |
| Linux/Mac | LF(\n) |
换行,一个字符 |
Git 默认有一个设置 core.autocrlf=true,意思是:
"你在 Windows 上干活?那我帮你把仓库里的 LF 文件转成 CRLF 放你硬盘上,这样你用 Windows 记事本不会乱。"
你看到的报错:
1 | |
这个 warning 本身不是错误,但刷几百条很烦人。
为什么不建议直接关掉 core.autocrlf
1 | |
关掉当然可以,但只对你这一台电脑有效。如果其他人 clone 这个项目,操作系统不同(Mac / Linux / Windows),换行符行为还是不一样,可能有人用 Windows 提交了 CRLF,有人用 Mac 提交了 LF,混在一起就很乱。
行业标准做法:.gitattributes
在项目根目录创建一个 .gitattributes
文件,放在项目里,任何电脑上 clone 都会自动遵守。
1 | |
逐行详细解释:
第一行:* text eol=lf
1 | |
这行是整个文件的核心。拆开看:
| 部分 | 作用 |
|---|---|
* |
通配符,匹配所有文件——不管后缀是什么(.js、.ts、.tsx、.py、.json、.md、.html、.css、.scss、.env、.yml、.toml、.sh、.c、.cpp……甚至没有后缀的文件如
.gitignore、Makefile、Dockerfile),全部命中 |
text |
告诉 Git:这是一个文本文件,不是二进制文件。Git 会对其做 diff、merge、统计行数等操作 |
eol=lf |
强制指定换行符为 LF。无论 clone 到 Windows、Mac
还是 Linux,工作目录里都是
\n。任何系统都不会弹换行符 warning |
💡 为什么不用
* text=auto?
auto只是让 Git 自动猜一个文件是不是文本,但换行符仍然受core.autocrlf影响,Windows 上照弹 warning。eol=lf是直接锁死,不受autocrlf干扰。
后续几行:*.png binary
等
1 | |
| 部分 | 作用 |
|---|---|
*.png |
匹配所有 .png 后缀文件 |
binary |
告诉 Git:这是二进制文件,不要动它。Git
不会尝试换行符转换,不会做文本 diff(只在 commit 里存 hash),也不会在
git diff 时显示内容 |
图片、字体文件如果被当作文本处理,里面的二进制字节会被换行符转换破坏,导致文件损坏。所以必须标
binary。
这条规则覆盖了哪些文件
因为第一行是
* text eol=lf(星号匹配所有),所以你的项目里:
| 类型 | 举例 | 是否被 * 覆盖 |
|---|---|---|
| JavaScript | app.js, utils.mjs |
✅ |
| TypeScript | page.ts, layout.tsx |
✅ |
| JSON | package.json, tsconfig.json |
✅ |
| Markdown | README.md |
✅ |
| CSS | globals.css, tailwind.scss |
✅ |
| HTML | index.html |
✅ |
| 配置文件 | .gitignore, .env,
.env.example, .prettierrc,
.eslintrc, next.config.mjs |
✅ |
| Shell/Python/C | setup.sh, main.py, app.c |
✅ |
| 无后缀文件 | Makefile, Dockerfile |
✅ |
| 图片 | logo.png, bg.jpg |
⚠️ 被 * 命中过,但下面
*.png binary 覆盖了 |
关键点:不需要单独列每种文件类型。*
通配符一网打尽,只有二进制文件需要单独标 binary
来排除。
设置完后执行:
1 | |
这会扫描所有文件,按 .gitattributes
的规则统一换行符。之后 git add .
就不会再刷换行符警告了。
设置前后对比
| 改之前 | 改之后 | |
|---|---|---|
| 警告方向 | LF will be replaced by CRLF |
CRLF will be replaced by LF |
| 含义 | 要把 LF 转成 Windows 的 CRLF | 要把 CRLF 转成 Linux 的 LF |
| 仓库存储 | 可能混着 CRLF 和 LF | 统一 LF ✅ |
可以看到警告仅在第一次 git add --renormalize
时出现一次,之后就没有了。
LF 在 Windows 上会不会出问题
以前只有老版 Windows 记事本不认识 LF(打开全挤在一行)。现代编辑器(VS Code、WebStorm、Sublime Text、Notepad++)全部兼容 LF,完全没有问题。所以放心用。
六、提交(commit)
做好了 .gitignore 和 .gitattributes
之后:
1 | |
-m 后面写备注,描述这次改了什么。好的备注习惯:
| 不好 | 好 |
|---|---|
| "更新" | "修复动滑轮钩子转向Bug" |
| "改了好多" | "添加 15 个测试场景,修复浮点溢出" |
| "final" | "添加 .gitignore 排除 node_modules" |
七、查看状态和历史
git status(最常用)
1 | |
告诉你当前工作目录和暂存区的状态。看到:
- "Changes to be committed" → 已经
git add了在等 commit - "Changes not staged for commit" → 改了但没 add
- "Untracked files" → 新增文件,还没让 Git 管
git log
1 | |
1 | |
最左边那串乱码(a1b2c3d)是 commit
hash,版本号。以后回滚、对比、查看都要用到它。
git log 进阶用法
1 | |
八、回滚操作(详细版)
先说说 commit 的结构
每次 git commit 就是一个快照。用
git log --oneline 可以看到一串提交历史:
1 | |
- 最左边那串乱码(
a1b2c3d)叫 commit hash,Git 自动生成的唯一编号 HEAD是一个指针,永远指向你当前所在的版本main是主分支的名字HEAD -> main表示你在 main 分支上,当前在a1b2c3d这个版本
场景 1:改坏了还没 commit
你改了一堆代码,但还没 git add 也没
git commit,想回到之前的安全状态:
1 | |
这个点 . 代表当前目录所有文件。git checkout
的意思是"把工作目录里的文件替换成上一次 commit 的版本"。
或者只恢复某个文件:
1 | |
场景 2:刚 commit 了,发现写错了
1 | |
| 部分 | 含义 |
|---|---|
reset |
重置 |
--soft |
温和模式,只动 commit 记录,不动你的修改 |
HEAD |
当前版本(那个短横线箭头) |
~1 |
往前一个版本 |
git reset --soft HEAD~1 的意思是:撤回最近一次
commit,但保留所有代码变更。你还可以继续改,改好了重新
git add . && git commit。
如果不想要改了,暴力模式:
1 | |
--hard
连文件修改一起丢掉。慎用!这个没法找回。
场景 3:回到了错误的版本想回来
如果不小心 git reset --hard 回错了,想恢复最后一次
commit:
1 | |
git reflog 会显示所有的 HEAD
移动记录,包括你 reset 之前的那个位置:
1 | |
找到你想要的 commit hash,然后 git reset --hard
回去:
1 | |
⚠️
git reflog只能找回几天内的记录。git reflog没有时效,只在你删掉.git文件夹时才会丢失。
场景 4:回到很久以前的某个版本
1 | |
这会进入"detached HEAD"状态(游离状态),意思是"你回到了历史版本看看,不在主分支上了"。
当你在 detached HEAD 状态下,千万不要修改文件并 commit! 如果这样做了:
1 | |
你的新 commit 没有挂在任何分支上,切回 main 后就找不到了。要提交的话应该先建一个分支:
1 | |
看完了想回来:
1 | |
场景 5:安全撤销某个历史提交(推荐)
1 | |
git revert
不会删掉那个提交,而是创建一个反向操作的提交。
比如你的历史是:
1 | |
执行 git revert a1b2c3d 后:
1 | |
历史不会丢失,只是多了一个"反着做的提交"。团队协作时推荐用 revert 而不是 reset,因为 reset 会改写历史,别人已经拉下去的话会冲突。
场景 6:先看改了啥再决定要不要回滚
1 | |
对比结果示例:
1 | |
场景 7:暂时存起来,不想提交
你正在改代码,突然要切出去干别的事,但改到一半不想 commit:
1 | |
你的修改被"藏"起来了,工作目录恢复到干净的 commit 状态。
想找回来:
1 | |
场景 8:打标签 —— 标记重要版本
1 | |
以后回到这个版本:
1 | |
推送到远程:
1 | |
回滚总结
| 情况 | 命令 | 影响 |
|---|---|---|
| 改坏了没 commit | git checkout . |
全盘恢复,改动全丢 |
| 只恢复一个文件 | git checkout 文件名 |
该文件回到 commit 状态 |
| 刚 commit 想重来 | git reset --soft HEAD~1 |
commit 撤回,改的代码还在 |
| commit 和改动都扔 | git reset --hard HEAD~1 |
找不回代码 |
| reset 回错了 | git reflog 找到旧 hash |
只要记得哈希值就能恢复 |
| 安全撤销历史提交 | git revert 版本号 |
新增反向提交,不删历史 ✅ |
| 先看改了啥 | git diff / git diff --cached |
对比改动,不实际执行 |
| 暂存(不提交) | git stash / git stash pop |
藏起来 / 拿回来 |
| 打标签标记版本 | git tag v1.0 |
稳定版本的锚点 |
九、远程仓库(详细版)
什么是远程仓库
远程仓库就是"另一台电脑上存的 Git 仓库"。你的代码在本地,同时存一份到远程(GitHub / 阿里云)作为备份和协作点。
git push = 本地推到远程 git pull =
从远程拉到本地 git clone =
从远程完整复制一个仓库到本地(第一次拉)
9.0 认证方式:HTTPS + Access Token
远程仓库有两类地址格式:
1 | |
SSH 需要配密钥对,临时电脑上比较麻烦。HTTPS + Access Token 更简单。
什么是 Access Token?
Token 就是一个临时代替密码的字符串。大多数 Git 托管平台(GitHub、Gitea、GitLab 等)都支持。
和密码比的区别: | | 密码 | Token | |------|------|------| | 永久有效 | ✅ | 可以设有效期 | | 权限控制 | 全权 | 可以限制只读/只写/某些仓库 | | 泄露后 | 改所有服务 | 删掉这一个 Token 就行 | | 二次验证 | 可能要走 | 不需要 |
怎么创建 Token
Gitea(自建 Git 服务): 打开
https://你的域名/user/settings/applications,"生成新的令牌",勾选仓库读写权限,复制生成的
Token。
GitHub: Settings → Developer settings → Personal
access tokens → Generate new token,勾选 repo 权限。
克隆时直接用 Token(推荐)
1 | |
这样克隆后,origin 的 URL 里已经带了 Token,之后
git push / git pull
不用再输任何密码。
如果已经克隆了,修改已有地址嵌入 Token
1 | |
另一个办法:让 Git 记住密码
1 | |
然后在第一次 push 时输入用户+Token,之后 Git
会存到本地文件(~/.git-credentials),不会再问。
⚠️ 这个文件是明文存的,只在你自己电脑上用,公共电脑不要开。
9.1 git remote
完整指令(详解)
git remote
管理本地的"远程仓库通讯录"。每个远程地址在本地的别名叫
origin 或你起的其他名字。
git remote -v(查看所有远程)
1 | |
输出示例:
1 | |
(fetch)= 拉取时用的地址(push)= 推送时用的地址- 两个通常一样,也可以配不一样
git remote add(添加新远程)
1 | |
| 部分 | 含义 |
|---|---|
remote add |
"往通讯录里加一条" |
origin |
这条记录的别名——你想叫什么都行(别人习惯叫 origin) |
https://... |
实际的仓库地址 |
可以加多个远程:
1 | |
💡
git remote add是新建一条记录。如果origin已经存在,会报fatal: remote origin already exists。
git remote set-url(修改已有远程地址)
1 | |
场景 1:换了服务器地址
1 | |
场景 2:URL 里嵌入 Token
1 | |
git remote add vs set-url 核心区别
git remote add |
git remote set-url |
|
|---|---|---|
| 干什么 | 新增一条通讯录 | 覆盖已有通讯录的地址 |
| 什么时候用 | 还没有 origin 时 |
已经有 origin,想改地址 |
origin 已存在 |
❌ 报错 | ✅ 静默覆盖 |
git remote rename(改别名)
1 | |
git remote remove(删除远程)
1 | |
💡 只删本地记录,不会影响远程服务器上的仓库。
git remote show(查看某个远程详情)
1 | |
输出示例:
1 | |
可以看到:fetch/push 地址、远程有哪些分支、本地分支和远程分支的对应关系。
第一步:在 GitHub 网页上创建空仓库
- 打开 github.com,登录
- 点右上角 + → New repository
- 仓库名随便填(如
physics-animation-9) - 务必选 Private(私人仓库,别人看不到)
- 不要勾 "Add a README" / ".gitignore" / "license"
- 点 Create repository
创建后 GitHub 会显示一个页面,上面有几行命令,那就是你要在本地执行的。
第二步:在本地关联远程仓库
1 | |
这条命令做了什么? - git remote add = "添加一个远程地址"
- origin = 给这个远程地址起的名字,习惯叫
origin(来源的意思) - https://... = 远程仓库的地址
起名的好处:以后不用再打一长串 URL,直接说
git push origin 就行。
第三步:把本地 main 分支推到远程
1 | |
-M= 强制改名(把默认分支名从 master 改成 main)-u= 关联(--set-upstream),告诉 Git "以后这个本地 main 分支就和远程 origin 的 main 分支绑定了"- 设了
-u之后,下次git push就可以直接git push,不用再写origin main
可能会遇到的登录问题:
1 | |
不想每次输密码可以缓存:
1 | |
第四步:后续日常推送
1 | |
9.2 推送到阿里云服务器
第一步:在服务器上初始化裸仓库(只需做一次)
1 | |
--bare
(裸仓库)是什么意思?普通仓库既存代码文件也存版本历史,但远程仓库不需要"工作目录",它只需要接收
push 和提供 pull。--bare
就是「只有版本历史,不存代码文件」的仓库。
第二步:在本地添加远程地址
1 | |
这次用的是 SSH 协议(root@IP:路径)而不是
HTTPS。好处:配了 SSH 免密登录后,每次 push 不用输密码。
第三步:推送到阿里云
1 | |
第一次可能会提示输入服务器密码。不想每次都输的话,配 SSH 免密:
1 | |
配好后 git push aliyun main 就不需要输密码了。
9.3 同时用 GitHub 和阿里云
1 | |
会显示:
1 | |
两个远程地址都可以推:
1 | |
9.4 git clone — 从远程下载仓库
如果你在另一台电脑上,想拉这个项目下来:
1 | |
git clone 会自动: - 创建一个文件夹(名字和仓库名一样)
- 初始化 .git - 拉下所有代码和版本历史 -
远程地址自动配好,直接就能 git push
9.5 git pull vs git fetch
| 命令 | 作用 | 区别 |
|---|---|---|
git fetch |
从远程下载最新版本信息 | 但不会自动合并到你当前的代码里 |
git pull |
下载 + 自动合并 | 等于 git fetch + git merge |
1 | |
9.6 常见远程报错
报错
1:fatal: remote origin already exists
远程 origin 已经存在了。说明你已经
git remote add 过一次。要么用另一个名字,要么
set-url 覆盖:
1 | |
报错
2:fatal: refusing to merge unrelated histories
本地仓库和远程仓库没有共同的提交历史。常见于你在平台上建仓库时勾选了 README / .gitignore。解决:
1 | |
💡 以后建远程仓库不要勾任何初始文件,保持完全空仓。
报错
3:Updates were rejected because the remote contains work that you do not have locally
远程有本地没有的提交(别人推了,或者你换了电脑)。先 pull 再 push:
1 | |
十、补充知识点
关于 2>/dev/null
1 | |
2>/dev/null 是 Linux/Unix 的写法: - 2 =
stderr(错误输出流) - > = 重定向 -
/dev/null = Linux 黑洞,所有扔进去的东西都消失
意思: 把错误信息扔进黑洞,不显示。
但这个写法在 PowerShell 不兼容。PowerShell 里应该这么写:
1 | |
$null 是 PowerShell
里的"空"。或者更干脆不加,报错了才知道具体哪里不对。
关于 git config 的作用域
1 | |
优先级:--local >
--global。也就是说,项目自己的配置会覆盖全局配置。
查看当前所有配置:
1 | |
关于 .gitignore 已跟踪文件的解除
已跟踪的文件必须先 git rm -r --cached . 再从 Git
的花名册中移除,否则 .gitignore 对它们无效。加
-f 强制,加 . 表示所有文件。
--cached 不会删硬盘文件,只管解除跟踪。这是安全的。
十一、完整操作流程(下次新建项目直接照搬)
1 | |
十二、今天遇到的报错速查表
| # | 报错 / 现象 | 原因 | 解决 |
|---|---|---|---|
| 1 | notepad .gitignore 找不到文件 |
Notepad 自动加了 .txt 后缀 |
用 PowerShell 创建 |
| 2 | .gitignore 设了但没效果 |
> 输出 UTF-16 |
改用 Set-Content -Encoding UTF8 |
| 3 | 2>/dev/null 报错 |
PowerShell 不是 bash | 改用 2>$null 或不加 |
| 4 | LF→CRLF 刷屏 |
core.autocrlf=true |
加 .gitattributes 用 eol=lf |
| 5 | Author identity unknown |
没配 name/email | git config --global |
| 6 | node_modules 还在被跟踪 |
已跟踪的文件 .gitignore 无效 |
git rm -r --cached . 先解除 |
| 7 | .gitignore 冲突
staged content different |
文件版本不一致 | 加 -f 强制 |
| 8 | git check-ignore 永远没输出 |
之前设了 core.excludesFile 干扰 |
--unset core.excludesFile |
| 9 | 找不到 .gitignore |
隐藏文件默认看不到 | 资源管理器 → 查看 → 隐藏的项目 |
| 10 | remote origin already exists |
远程已存在同名远程 | 换名或用 set-url 覆盖 |
| 11 | refusing to merge unrelated histories |
本地和远程没有共同历史 | --allow-unrelated-histories |
| 12 | Updates were rejected |
远程比本地新 | 先 git pull 再推送 |
写完这份笔记,相当于 Git 新手路上的弯弯绕绕都记下来了。下次建新项目直接照着流程来,不会再踩同样的坑。
有问题随时问 🌟