跳转到内容

第 7 章 · Git 基础

一句话:记录每次改动、随时回到任何版本、多人同时改一份代码而不互相覆盖的版本控制工具。 用它还是用别的:所有代码项目都用,哪怕只有你一个人;二进制大文件(视频、模型权重)另用 Git LFS 或网盘。 核对版本:git 2.45,2026-10

Windows:

终端窗口
winget install Git.Git

macOS:

终端窗口
brew install git

Ubuntu 和 Debian:

终端窗口
sudo apt install git

Windows 装好后,已经打开的终端要关掉重开,git 命令才能用。安装包还会带一个 Git Bash,本书的命令在 PowerShell 7 里就能跑,不需要它。

查版本:

终端窗口
git --version

装完先做三条配置:名字、邮箱、默认分支名。不设名字和邮箱,第一次提交就会报错让你补上。步骤见 7.4 的第一个工作流。

  • 仓库(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 的位置。

Git 的四个区

表按一次改动的先后排:先配置,再建立或下载仓库,然后在日常循环里反复暂存、提交,需要时开分支、和远程同步。命令里的 <文件> 可以是一个文件,也可以是目录或 .(当前目录下全部)。<提交> 写哈希的前七位,或 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 章。

每台机器只做一次。名字和邮箱会写进之后的每一个提交,设错了,这些提交上的作者就都是错的:

  1. 设名字:git config --global user.name "<名字>"。
  2. 设邮箱:git config --global user.email "<邮箱>"。
  3. 新仓库默认分支叫 main:git config --global init.defaultBranch main。
  4. Windows 上设换行处理:git config --global core.autocrlf input。
  5. 提交说明用 VS Code 写:git config --global core.editor "code --wait"。
  6. 确认写进去了:git config --list。

手上有一个还没用 Git 管理的项目,要放到 GitHub 上:

  1. 进入项目目录,运行 git init。
  2. 在项目根目录写 .gitignore,从 github/gitignore 仓库里挑对应语言的模板,写法见 7.5。
  3. 暂存全部文件:git add -A。
  4. 第一次提交:git commit -m "初始提交"。
  5. 在 GitHub 网页上新建一个空仓库,不勾选 README 和 .gitignore(详见第 9 章),复制它的地址。
  6. 添加远程:git remote add origin <url>。
  7. 推送:git push -u origin main。

每个功能单独开一个分支,main 始终保持能运行:

  1. 回到主分支:git switch main。
  2. 取回别人的新提交:git pull。
  3. 开新分支:git switch -c feature/<名>。
  4. 改代码,然后挑出这次要提交的改动:git add -p。
  5. 提交:git commit -m "<说明>"。改一点提交一次,重复第 4、5 步。
  6. 推送分支:git push -u origin feature/<名>。
  7. 在 GitHub 上开拉取请求(Pull Request,PR),等审查合并(详见第 9 章)。
  8. 合并后回到主分支:git switch main。
  9. 取回合并结果:git pull。
  10. 删掉本地的功能分支:git branch -d feature/<名>。

两个人改了同一个文件的同一处,git pull 或 git merge 时报 CONFLICT:

  1. 看哪些文件冲突:git status,列在 “both modified” 下的就是。

  2. 打开冲突文件,找到下面这样的三行标记。上半段是你的,下半段是对方的:

    <<<<<<< HEAD
    你本地的内容
    =======
    远程的内容
    >>>>>>> origin/main
  3. 留下想要的内容,可以是其中一段,也可以两段合起来,然后删掉三行标记。

  4. 标记为已解决:git add <文件>。

  5. 所有冲突文件都处理完后,完成合并:git commit。

  6. 改乱了想从头来:git merge --abort,回到合并之前的状态。

如果你设了 pull.rebase true(见 7.5),冲突发生在变基过程中:改完 git add <文件> 后用 git rebase --continue 代替 git commit,放弃用 git rebase --abort;此时标记上半段是对方的、下半段是你的,和合并时相反。

配置文件的位置:

范围 路径
全局配置(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 = 张三 # 提交的作者名
email = zhangsan@example.com # 与 GitHub 账号的邮箱一致
[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
.env
dist/
*.log

Node 项目把前三行换成 node_modules/。

  • 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"。