第 7 章 · Git 基础
一句话:记录每次改动、随时回到任何版本、多人同时改一份代码而不互相覆盖的版本控制工具。 用它还是用别的:所有代码项目都用,哪怕只有你一个人;二进制大文件(视频、模型权重)另用 Git LFS 或网盘。 核对版本:git 2.45,2026-10
7.1 安装与版本
Section titled “7.1 安装与版本”Windows:
winget install Git.GitmacOS:
brew install gitUbuntu 和 Debian:
sudo apt install gitWindows 装好后,已经打开的终端要关掉重开,git 命令才能用。安装包还会带一个 Git Bash,本书的命令在 PowerShell 7 里就能跑,不需要它。
查版本:
git --version装完先做三条配置:名字、邮箱、默认分支名。不设名字和邮箱,第一次提交就会报错让你补上。步骤见 7.4 的第一个工作流。
7.2 五分钟概念
Section titled “7.2 五分钟概念”- 仓库(Repository):被 Git 管理的项目文件夹。所有历史都存在项目根目录下隐藏的
.git目录里,删掉它,项目就变回普通文件夹,历史全部丢失。 - 四个区:一次改动要经过四个地方才到达别人手里。你编辑文件的地方是工作区(Working Directory);
git add把改动放进暂存区(Staging Area),挑好这次要提交哪些;git commit把暂存区存成一个版本,进入本地仓库(Local Repository);git push把本地仓库的新提交送到远程仓库(Remote),git fetch反过来把远程的新提交取回本地。 - 提交(Commit):一个快照加一条说明。每个提交有一串哈希(hash)作为编号,如
a926c7f,命令里用前七位就够。提交之后的内容随时可以找回,没提交的改动丢了就是丢了。说明写给半年后的自己看,一句话讲清做了什么、为什么做。 - 分支(Branch):指向某个提交的可移动指针。在分支上每提交一次,指针就往前挪一格。新仓库的默认分支叫
main,做新功能时开一个新分支,改坏了也不影响main。 - HEAD:你现在站在哪个提交上。通常它跟着当前分支走;直接切到某个提交时,HEAD 就不在任何分支上了,见 7.6。
- 远程(Remote):另一台机器上的同一个仓库,比如 GitHub 上那份。
git clone下来的仓库自动有一个叫origin的远程,origin/main是你上次同步时远程main的位置。
7.3 常用命令
Section titled “7.3 常用命令”表按一次改动的先后排:先配置,再建立或下载仓库,然后在日常循环里反复暂存、提交,需要时开分支、和远程同步。命令里的 <文件> 可以是一个文件,也可以是目录或 .(当前目录下全部)。<提交> 写哈希的前七位,或 HEAD~1(当前提交的上一个)这类相对写法。
| 任务 | 命令 | 说明 |
|---|---|---|
| 配置 | ||
| 设置名字 | git config --global user.name "<名字>" |
写进每个提交,别人看到的作者名 |
| 设置邮箱 | git config --global user.email "<邮箱>" |
和 GitHub 账号的邮箱一致,提交才会关联到你 |
| 换行处理 | git config --global core.autocrlf input |
提交时统一成 LF;Windows 写 true 的后果见 7.6 |
| 查看全部配置 | git config --list --show-origin |
每项后面跟着它来自哪个文件 |
| 开始 | ||
| 把当前目录变成仓库 | git init |
生成 .git 目录,已有文件不受影响 |
| 下载一个仓库 | git clone <url> |
在当前目录下新建同名文件夹 |
| 只下载最新版 | git clone --depth 1 <url> |
不要历史,大仓库快很多 |
| 日常循环 | ||
| 看当前状态 | git status |
哪些改了、哪些已暂存;加 -s 每个文件一行 |
| 暂存一个文件 | git add <文件> |
下次提交带上它 |
| 暂存全部改动 | git add -A |
包括新建和删除的文件,先看 git status 再用 |
| 逐块挑选暂存 | git add -p |
每块改动问一次,y 要 n 不要 q 退出 |
| 提交 | git commit -m "<说明>" |
说明写这次做了什么,不写“修改” |
| 修改上一次提交 | git commit --amend |
补文件或改说明;只在没 push 时用 |
| 看未暂存的改动 | git diff |
工作区对比暂存区 |
| 看已暂存的改动 | git diff --staged |
提交前最后核对一遍 |
| 看提交历史 | git log --oneline --graph --all |
一行一个提交,画出所有分支的走向 |
| 看某个提交改了什么 | git show <提交> |
说明和改动一起显示 |
| 分支 | ||
| 列出分支 | git branch |
当前分支前有 *;加 -a 连远程分支一起列 |
| 新建并切换 | git switch -c <名> |
从当前提交分出去 |
| 切换分支 | git switch <名> |
有没提交的改动时可能拒绝切换,先提交或暂存现场(stash,见下方) |
| 合并分支 | git merge <名> |
把 <名> 合进当前分支 |
| 删除分支 | git branch -d <名> |
没合并过的会拒绝删除,防止丢提交 |
| 远程 | ||
| 看远程地址 | git remote -v |
每个远程列两行,拉取和推送 |
| 添加远程 | git remote add origin <url> |
本地 git init 的仓库要先加这一步才能 push |
| 取回远程的新提交 | git fetch |
只下载,不动你的工作区 |
| 拉取并合并 | git pull |
等于 git fetch 加 git merge |
| 拉取并变基(rebase) | git pull --rebase |
把你的提交接到远程最新提交后面,历史是一条直线 |
| 第一次推送新分支 | git push -u origin <分支> |
-u 记住对应关系,之后直接 git push |
| 推送 | git push |
把本地新提交送到远程 |
| 改写历史后推送 | git push --force-with-lease |
远程有别人的新提交时会拒绝,比 --force 安全 |
| 暂存现场 | ||
| 先收起改动 | git stash |
工作区变干净,好去切分支;加 -u 连新建的文件一起收 |
| 放回改动 | git stash pop |
放回最近收起的一份;收过多次先用 git stash list 看 |
| 撤销(最常用四条) | ||
| 丢掉工作区的改动 | git restore <文件> |
文件回到上次暂存或提交时的样子,改动找不回 |
| 取消暂存 | git restore --staged <文件> |
从暂存区拿出来,改动还在工作区 |
| 撤销上一次提交 | git reset --soft HEAD~1 |
提交没了,改动回到暂存区 |
| 用新提交抵消旧提交 | git revert <提交> |
已经 push 的提交用它撤,不改历史 |
| 标签(tag) | ||
| 打版本标签 | git tag -a v1.0 -m "<说明>" |
给当前提交起个固定名字 |
| 推送标签 | git push --tags |
普通 git push 不带标签 |
| 看谁改的 | ||
| 每行最后是谁改的 | git blame <文件> |
每行前面是提交、作者和日期 |
| 一个文件的全部改动 | git log -p <文件> |
从新到旧,每次提交的改动内容 |
误删分支、找回丢失的提交、改写历史,见第 8 章。
7.4 典型工作流
Section titled “7.4 典型工作流”装完 Git 的第一次配置
Section titled “装完 Git 的第一次配置”每台机器只做一次。名字和邮箱会写进之后的每一个提交,设错了,这些提交上的作者就都是错的:
- 设名字:
git config --global user.name "<名字>"。 - 设邮箱:
git config --global user.email "<邮箱>"。 - 新仓库默认分支叫
main:git config --global init.defaultBranch main。 - Windows 上设换行处理:
git config --global core.autocrlf input。 - 提交说明用 VS Code 写:
git config --global core.editor "code --wait"。 - 确认写进去了:
git config --list。
把本地项目推到远程
Section titled “把本地项目推到远程”手上有一个还没用 Git 管理的项目,要放到 GitHub 上:
- 进入项目目录,运行
git init。 - 在项目根目录写
.gitignore,从 github/gitignore 仓库里挑对应语言的模板,写法见 7.5。 - 暂存全部文件:
git add -A。 - 第一次提交:
git commit -m "初始提交"。 - 在 GitHub 网页上新建一个空仓库,不勾选 README 和
.gitignore(详见第 9 章),复制它的地址。 - 添加远程:
git remote add origin <url>。 - 推送:
git push -u origin main。
一天的分支开发
Section titled “一天的分支开发”每个功能单独开一个分支,main 始终保持能运行:
- 回到主分支:
git switch main。 - 取回别人的新提交:
git pull。 - 开新分支:
git switch -c feature/<名>。 - 改代码,然后挑出这次要提交的改动:
git add -p。 - 提交:
git commit -m "<说明>"。改一点提交一次,重复第 4、5 步。 - 推送分支:
git push -u origin feature/<名>。 - 在 GitHub 上开拉取请求(Pull Request,PR),等审查合并(详见第 9 章)。
- 合并后回到主分支:
git switch main。 - 取回合并结果:
git pull。 - 删掉本地的功能分支:
git branch -d feature/<名>。
解决一次合并冲突
Section titled “解决一次合并冲突”两个人改了同一个文件的同一处,git pull 或 git merge 时报 CONFLICT:
-
看哪些文件冲突:
git status,列在 “both modified” 下的就是。 -
打开冲突文件,找到下面这样的三行标记。上半段是你的,下半段是对方的:
<<<<<<< HEAD你本地的内容=======远程的内容>>>>>>> origin/main -
留下想要的内容,可以是其中一段,也可以两段合起来,然后删掉三行标记。
-
标记为已解决:
git add <文件>。 -
所有冲突文件都处理完后,完成合并:
git commit。 -
改乱了想从头来:
git merge --abort,回到合并之前的状态。
如果你设了 pull.rebase true(见 7.5),冲突发生在变基过程中:改完 git add <文件> 后用 git rebase --continue 代替 git commit,放弃用 git rebase --abort;此时标记上半段是对方的、下半段是你的,和合并时相反。
7.5 配置要点
Section titled “7.5 配置要点”配置文件的位置:
| 范围 | 路径 |
|---|---|
| 全局配置(Windows) | %USERPROFILE%\.gitconfig |
| 全局配置(macOS) | ~/.gitconfig |
| 全局配置(Linux) | ~/.gitconfig |
| 仓库配置 | 项目里的 .git/config |
| 忽略规则 | 项目根目录的 .gitignore |
| 换行等文件属性 | 项目根目录的 .gitattributes |
同一项在仓库配置和全局配置里都写了时,以仓库配置为准,比如公司项目用公司邮箱、个人项目用私人邮箱。git config --global 写的是全局配置文件,不加 --global 写的是当前仓库的 .git/config。.gitignore 和 .gitattributes 要提交进仓库,团队成员拉下来就是同一套规则。
值得改的项:
| 配置项 | 建议值 | 效果 |
|---|---|---|
user.name |
你的名字 | 提交的作者名 |
user.email |
GitHub 账号的邮箱 | 提交关联到你的 GitHub 账号 |
init.defaultBranch |
main |
新仓库默认分支叫 main |
core.autocrlf |
input |
提交时把 CRLF 换成 LF,检出时不转换 |
core.quotepath |
false |
中文文件名正常显示 |
pull.rebase |
true |
git pull 默认变基,不产生多余的合并提交 |
push.autoSetupRemote |
true |
新分支第一次 git push 不用加 -u |
alias.lg |
"log --oneline --graph --all" |
输入 git lg 看提交图 |
core.editor |
"code --wait" |
提交说明和合并说明在 VS Code 里写 |
设好 pull.rebase 和 push.autoSetupRemote 之后,git pull --rebase 和 git push -u origin <分支> 都可以简写成 git pull、git push。
最小配置。可以直接编辑全局配置文件,把下面的名字和邮箱换成自己的;也可以按上表逐条运行 git config --global <配置项> <值>:
[user] name = 张三 # 提交的作者名[init] defaultBranch = main # 新仓库的默认分支[core] autocrlf = input # 提交时 CRLF 转 LF,检出不转 quotepath = false # 中文文件名原样显示 editor = code --wait # 用 VS Code 写提交说明[pull] rebase = true # pull 时变基,历史保持一条直线[push] autoSetupRemote = true # 新分支第一次 push 不用加 -u[alias] lg = log --oneline --graph --all # git lg 看所有分支的提交图Python 项目的最小 .gitignore,依次是虚拟环境、字节码缓存目录、字节码文件、存密钥的环境变量文件、构建产物、日志:
.venv/__pycache__/*.pyc.envdist/*.logNode 项目把前三行换成 node_modules/。
7.6 注意事项
Section titled “7.6 注意事项”warning: in the working copy of '<文件>', LF will be replaced by CRLF。 原因:Windows 上core.autocrlf常为true。处理:git config --global core.autocrlf input;.gitattributes写* text=auto eol=lf。git status或git log里中文文件名显示成"\346\226\207\344\273\266.txt"。 原因:core.quotepath默认把非英文字符转义。处理:git config --global core.quotepath false。- push 被拒,
! [rejected] main -> main (non-fast-forward)或(fetch first)。 原因:远程有你本地没有的提交。处理:先git pull --rebase再git push;别用--force,会覆盖掉别人的提交。 fatal: refusing to merge unrelated histories。 原因:本地和远程各自独立初始化过,比如建 GitHub 仓库时勾了 README。处理:git pull origin main --allow-unrelated-histories,只在第一次合并时用。You are in 'detached HEAD' state。 原因:切到了某个提交而不是某个分支,这时的提交不属于任何分支,切走后很难找回。处理:要保留改动就git switch -c <新分支>,不要就git switch main。- 误把
node_modules或几百 MB 的文件提交了,push 很慢或被拒。 原因:没写.gitignore就git add -A。处理:没 push 时运行git rm -r --cached <目录>,把它补进.gitignore,再git commit --amend;已经 push 了见第 8 章。 - 提交了密码或密钥。 原因:存密钥的配置文件没进
.gitignore。处理:立刻在服务端作废那个密钥,再按第 8 章改写历史;只删文件再提交没用,历史里还在。 git pull之后代码没变。 原因:你在别的分支上,拉的是那个分支。处理:git branch看当前分支(前面带*的那个),再git switch main。git commit时终端里弹出 vim,不知道怎么退出。 原因:没设编辑器,Git 用了默认的 vim。处理:按Esc后输入:q!回车退出,普通提交会取消;--amend和合并提交要用:cq才会取消。然后运行git config --global core.editor "code --wait"。
7.7 深入阅读
Section titled “7.7 深入阅读”- Git 官方参考手册:每条命令的全部选项,查参数到这里。
- Pro Git 第 2 版(中文):免费的官方教材,前三章读完就够日常用。
- Learn Git Branching:在浏览器里可视化练分支、合并、rebase。
- gitignore 模板:各语言和编辑器的 .gitignore,复制就能用。
- GitHub 的 Git 入门:从 GitHub 角度讲 Git 日常操作,有中文。