Neoverse-Docs

1.15 版本控制与 Git

版本控制概念、Git 核心工作流与进阶操作,涵盖分支、合并、撤销与远程协作

主要编写者:
本节 AI 摘要

本节围绕工作区、暂存区和提交历史,介Git 的日常提交、分支协作、冲突处理与撤销方法。重点是先判断改动位于哪里,再选择不会误伤其他内容的命令

一、版本控制

版本控制(Version Control)用于记录我们主动提交的文件状态,便于比较、回溯和协作。它不会自动保存每一次按键,也不能代替未提交文件的备份。

1.1 版本控制的演进

阶段代表工具特点
手动版复制文件夹改名 v1v2原始,容易乱
本地版本控制RCS单机,无法协作
集中式SVN、CVS主要历史保存在中央服务器,离线操作能力受限
分布式Git、Mercurial每人都有完整历史,离线可用

1.2 Git 是什么

版本控制是一个概念,Git 是其中最流行的一个具体实现。

Git 诞生于 2005 年,作者是 Linux 内核创始人 Linus Torvalds,最初用于管理 Linux 内核这样的大型代码库。它属于上一节表格里的“分布式”一类,主要有两个特点:

  • 每人都有完整历史:克隆仓库时,不仅拿到最新文件,也拿到全部提交历史。没有网络也能提交、查看历史、创建分支,联网后再同步。
  • 按快照记录:每次提交保存一份当时的文件快照,而不是只记录文件之间的差异。因此切换分支、回到任意历史版本都很快。

与集中式系统(如 SVN)相比,客户端不再依赖中央服务器才能查看历史或提交;服务器宕机也不会让你丢失本地历史。这些特性让 Git 成为当前使用最广泛的版本控制系统。

为什么要用 Git?

其实你不一定要用 Git,也不一定要用任何源码管理系统。只需要满足以下条件:

  • 有足够的脑力,清楚自己在干嘛、一周前干了啥、当时为啥这么干
  • 有足够的耐心,在不小心删掉写了一整天的代码之后重新写一遍
  • 有足够的能力独立开发,不需要深入合作;或者能接受用微信、邮件等互相发代码来同步更改

这些要求显然是很容易达到的个鬼

Git 为我们提供了更好的解决方案,如:

  • git blame:查看每一行代码是谁写的、什么时候改的
  • git log / git cherry-pick:回滚更改,或将某些更改应用到其他分支
  • git branch:在不同分支上开发不同功能,多人协作互不干扰

二、Git 的核心概念

2.1 三个区域

Git 把文件分为三个区域,理解这三个区域是掌握 Git 的关键。

区域说明对应位置
工作区我们正在编辑的目录实际文件
暂存区准备提交的快照.git/index
本地仓库已提交的版本历史.git/ 目录
远程仓库托管在服务器上的仓库GitHub / CNB 等

INFO

暂存区让我们能精细控制“哪些改动一起提交”。一次改动可能涉及多个功能,我们可以分多次暂存、分多次提交,让历史清晰可读。

2.2 提交(Commit)

提交是 Git 的基本单位,每次提交记录一个完整的快照,包含:

  • 改动的内容
  • 作者、时间
  • 提交信息(message)
  • 指向父提交的引用;合并提交可以有多个父提交

提交通过父子关系形成有向无环图。没有分支合并时看起来像一条时间线,发生分叉与合并后则形成多条相互关联的历史线。

2.3 分支(Branch)

分支是指向某个提交的可移动引用。开发者可以从当前提交创建分支,在独立的提交历史上工作,再按项目流程合并或变基。

main 是主分支,feature 从 B 分出,独立开发 E、F 两个提交,完成后合并回 main

分支的代价很小

Git 分支本身只是一个引用,创建成本很低。是否为每项工作创建分支,应遵循仓库的协作流程;团队项目通常会用短生命周期分支隔离功能、修复或实验性改动。

三、初始配置

安装 Git 后(安装步骤见 1.14 Windows 环境配置1.14 WSL 环境配置),第一件事是配置身份:

Bash
# 配置用户名和邮箱 (每次提交都会带上)
git config --global user.name "<你的名字>"
git config --global user.email "<你的邮箱>"

git pull 的整合策略应按团队约定选择 merge、rebase 或仅快进,不必在尚未理解差异时写入全局配置。

验证配置:

Bash
git config --list --show-origin

3.1 三层配置与覆盖关系

Git 配置可以来自系统级、全局级和仓库级文件。更具体的配置通常覆盖更宽范围的配置:

层级常见文件影响范围
系统级/etc/gitconfig 或 Git 安装目录中的配置文件机器上的所有用户和仓库
全局级~/.gitconfig~/.config/git/config当前用户的所有仓库
本地级当前仓库的 .git/config当前仓库

查看某一层级的配置来源:

Bash
git config --system --list --show-origin
git config --global --list --show-origin
git config --local --list --show-origin

--system 可能需要管理员权限;没有对应文件或权限不足时命令可能失败。项目专属设置应写入本地级配置,个人默认值写入全局级配置。涉及身份、代理和凭据时,先确认当前来源,再修改。

提交邮箱与账户关联

如果希望提交关联到 GitHub 账户,应使用 GitHub 账户中已验证的邮箱或 GitHub 提供的 noreply 邮箱(未添加个人邮箱到 GitHub 账户时,不推荐)。提交身份与登录凭据两者并不同,修改前可用 git config --global --get user.email 检查本地配置的邮箱。

四、基本工作流

4.1 方法一:从零开始一个项目

从零初始化并推送一个项目

先在代码托管平台(如 GitHub、CNB 等)创建一个不含初始提交的空仓库,再在本地执行:

Bash
# 创建新目录并初始化仓库
mkdir my-project && cd my-project
git init

# 创建文件
printf '# My Project\n' > README.md

# 查看状态
git status

# 暂存
git add README.md         # 暂存指定文件

# 提交
git commit -m "Initial commit"

# 关联远程仓库并推送
git remote add origin https://github.com/你的用户名/项目名.git
git branch -M main
git push -u origin main

验证: 运行 git log --oneline,应看到刚才的提交记录。

4.2 方法二:克隆已有项目

Bash
# 克隆远程仓库到本地
git clone https://github.com/用户/项目名.git
cd 项目名

验证: 运行 git remote -v,应看到 origin 远程地址。

4.3 日常循环

绝大多数时候我们在重复这个循环:

Bash
# 改代码...
git status              # 看改了什么
git diff                # 看具体改动
git add <>             # 暂存
git commit -m "<说明>"     # 提交
git push                # 推送到远程

五、查看历史与差异

5.1 git log:查看提交历史

Bash
git log                 # 完整历史
git log --oneline       # 每条一行,简洁显示
git log --oneline --graph  # 图形化显示分支 (推荐)
git log -5              # 最近 5 条
git log --author="SSJ"  # 按作者过滤

每次提交都会获得一个唯一的哈希值(SHA-1),git log --oneline 每行开头的 a1b2c3d 就是它的缩写。后续命令示例中的 a1b2c3d 只是占位,实际使用时替换为 git log 中查到的真实哈希。

5.2 git diff:查看差异

Bash
git diff               # 工作区 vs 暂存区
git diff --staged      # 暂存区 vs 最新提交
git diff HEAD          # 工作区 vs 最新提交
git diff v1 v2         # 两个提交之间的差异

关于 --graph 选项

git log --oneline --graph --all 会用文本图展示各引用之间的提交关系。经常使用时可配置别名:git config --global alias.lg "log --oneline --graph --all",之后运行 git lg

六、暂存更改

不必一次暂存所有改动,可以只选择一部分内容暂存:

Bash
git add file1.txt file2.txt      # 暂存指定文件
git add -- '*.py'                # 按 Git 路径规格暂存仓库中的 .py 文件
git add -p                       # 交互式选择要暂存的代码块 (推荐)

关于 git add -p

git add -p(patch 模式)会把差异分成若干块,逐块询问是否暂存。一次工作混有多个目的时,可以借此拆分提交;如果同一差异块仍包含两类改动,还可以继续拆分或先回到编辑器整理。

七、提交信息规范

好的提交信息让历史可读、可追溯。推荐 Conventional Commits 规范:

Text
<类型>: <简短描述>

<可选的详细说明>

常见类型:

类型说明示例
feat新功能feat: 添加用户登录功能
fix修复 bugfix: 修复登录页面闪烁问题
docs文档改动docs: 更新 README 安装步骤
style格式调整(不影响代码逻辑)style: 统一缩进为 2 空格
refactor重构(非新功能、非修 bug)refactor: 抽离用户验证逻辑
test测试相关test: 补充登录单元测试
chore构建、依赖等维护工作chore: 更新构建配置

示例:

Text
feat: 添加用户登录功能

实现 JWT 认证,新增 /login 接口

本项目的提交规范

本项目也采用类似规范,详见 提交信息规范

提交前后都可能需要修正改动。应先判断内容位于工作区、暂存区还是提交历史。

八、撤销与回退

先运行 git statusgit diff 判断改动位于工作区、暂存区还是提交历史,再选择命令。名称相似的命令作用范围不同,不要靠试错使用。

场景建议命令是否改写历史
取消暂存,保留工作区内容git restore --staged -- <文件>
丢弃某个文件尚未暂存的改动git restore -- <文件>否,但文件内容会丢失
修正尚未共享的最近提交git commit --amend
撤销已推送到远程的提交git revert a1b2c3d否,新增一条反向提交

git restore 也可能丢失内容

git restore -- <文件> 会覆盖尚未提交的工作区改动。执行前先查看 git diff -- <文件>;不确定时,复制补丁、创建临时分支或先提交一个可识别的临时版本。

reset --hard 什么时候用?

git reset --hard 会同时移动分支并覆盖工作区和暂存区,初学阶段通常没有必要使用。若确有需要,先确认目标提交、备份未提交内容,并理解它会改写哪些引用;已推送到远程的历史优先使用 git revert

九、分支操作

9.1 基本分支命令

Bash
git branch                    # 查看本地分支
git branch -a                 # 查看所有分支 (含远程)
git branch feature            # 创建 feature 分支
git checkout feature          # 兼容旧教程:切换到 feature
git checkout -b feature       # 兼容旧教程:创建并切换
git switch feature            # 使用专用命令切换分支
git switch -c feature         # 创建并切换

git branch -d feature         # 删除已合并的分支
git branch -D feature         # 强制删除

9.2 合并(Merge)

把分支的改动合到当前分支:

Bash
git switch main               # 先切到目标分支
git merge feature             # 合并 feature

如果要合并的分支已经包含当前分支的全部提交,Git 可能执行 fast-forward:只移动当前分支引用,不创建新的合并提交。如果两条分支在分叉点之后都产生了新提交,Git 通常需要基于分叉点执行 three-way merge,并可能创建一个合并提交;无法自动决定的文件会产生冲突(见下节)。是否强制创建合并提交,可按项目约定使用 --no-ff

Bash
git merge --ff-only feature   # 只能快进;不能快进时失败,不自动创建合并提交
git merge --no-ff feature     # 即使能快进也创建合并提交

fast-forward:main 在分叉点之后没有新提交时

main 只是把引用直接移到 feature 顶端(F),不产生新的合并提交。

three-way merge:两条分支在分叉点之后都产生了新提交

两条分支在分叉点 B 之后各自前进,Git 基于分叉点做三方合并,产生合并提交 M。

9.3 变基(Rebase)

rebase 是另一种整合改动的方式,它把我们的提交“搬到”目标分支末尾,让历史保持线性。

Bash
git switch feature
git rebase main               # 把 feature 的提交基于 main 重新应用

rebase 前:feature 从 B 分叉,main 已有新提交 C

rebase 后:E、F 被搬到 C 之后重新应用,历史从分叉变为一条直线

rebase 后 feature 看起来像从 main 顶端(C)接着开发,E'、F' 是重新生成的新提交(ID 与原 E、F 不同),所以不要擅自变基已推送到远程的分支。

方式历史冲突处理
merge保留分支结构,有合并节点一次解决
rebase线性历史,更整洁可能逐个提交解决

不要擅自改写共享历史

rebase 会为被重放的提交生成新的提交 ID。已经有人基于旧提交继续工作的分支,不应在没有协调的情况下变基并强制推送。个人分支是否允许整理后使用 --force-with-lease,以项目约定为准。初学阶段先在尚未推送的本地分支上练习。

9.4 detached HEAD

通常 HEAD 指向当前分支。

执行 git checkout a1b2c3dgit switch --detach a1b2c3d 后,HEAD 会直接指向某个提交,处于 detached HEAD 状态。此时可以查看历史或临时测试,但直接创建的新提交不属于任何分支,离开前应创建分支保存它:

Bash
git switch --detach a1b2c3d
# 临时查看或测试

git switch -c experiment       # 从当前提交创建分支并保存后续提交
# 或回到已有分支
git switch main

查看当前状态:

Bash
git status

十、解决冲突

当两个人改了同一行代码,合并时 Git 无法自动决定保留哪个,就会产生冲突。

Text
$ git merge feature
Auto-merging main.py
CONFLICT (content): Merge conflict in main.py

打开冲突文件,会看到:

Text
def hello():
<<<<<<< HEAD
    print("Hello from main")
=======
    print("Hello from feature")
>>>>>>> feature
  • <<<<<<< HEAD======= 是当前分支的内容
  • =======>>>>>>> feature 是传入分支的内容

解决方法:手动编辑保留想要的版本(或两者结合),删掉冲突标记,然后:

Bash
git add main.py        # 标记冲突已解决
git commit             # 完成合并

VS Code 可视化解决冲突

VS Code 可以识别冲突标记,并提供合并编辑器或行内操作。按钮名称和界面会随版本与语言设置变化;无论使用哪种界面,完成后都要检查最终文件,确认冲突标记已经删除,再运行测试并执行 git add

十一、远程仓库操作

远程仓库常用命令速查
Bash
# 查看远程
git remote -v

# 添加远程(注意:此处的 GithubName 为实际用户名不是昵称!)
git remote add origin https://github.com/GithubName/ProjectName.git

# 修改远程地址
git remote set-url origin 新地址

# 推送
git push origin main           # 推送 main 分支
git push -u origin feature     # 首次推送并建立追踪
git push --force-with-lease origin feature  # 谨慎覆盖远程分支,先检查远程跟踪状态

# 拉取
git pull                       # 获取远程更新后按配置执行 merge、rebase 或仅快进
git fetch                      # 只更新远程跟踪引用,不整合当前分支
命令行为适用场景
git fetch更新远程跟踪引用,不自动整合当前分支想先检查远程变化
git pull先 fetch,再按参数或配置整合当前分支已明确 merge、rebase 或仅快进策略时
git push推送本地提交到远程提交后同步
git remote -v查看远程仓库地址确认远程配置

先 fetch 再 merge

不确定远程变化时,可以先 fetch,再用 git log --oneline --graph --allgit diff 检查分支差异。使用 git pull 前应明确仓库约定的整合策略,避免意外创建合并提交或改写本地提交。

十二、忽略文件:.gitignore

有些文件不该进版本控制(临时文件、密钥、构建产物),用 .gitignore 指定:

常用 .gitignore 模板
.gitignore
# 编译产物
*.o
*.exe
build/

# 依赖
node_modules/
__pycache__/

# IDE 本地状态;是否忽略共享配置由团队决定
.idea/

# 系统文件
.DS_Store
Thumbs.db

# 敏感信息
.env
.env.*
!.env.example
*.pem

不要把敏感信息提交进仓库

私钥、密码、访问令牌,以及包含这些信息的配置文件(如 .env),都不应进入版本控制。敏感信息一旦提交,即使随后删除,仍可能存在于历史、分支、缓存或克隆中;发现泄露后应立即撤销或轮换凭据,再按平台指引清理历史。不含敏感信息的模板文件(如 .env.example)可以按项目需要提交。

.gitignore 只影响尚未跟踪的路径,不能让已跟踪文件自动退出版本控制。可参考 github/gitignore 的模板,再根据项目实际产物和编辑器配置调整。

十三、常用技巧

13.1 stash:临时保存改动

工作到一半要切分支处理紧急问题,又不想提交半成品:

Bash
git stash push         # 保存已跟踪文件的工作区与暂存区改动;默认不含未跟踪文件
git switch main        # 切走处理别的事
# ...处理完...
git switch feature     # 切回来
git stash list         # 确认要恢复的条目
git stash pop          # 应用最近条目,成功后从 stash 列表删除

13.2 tag:打标签

发布版本时打标签:

Bash
git tag v1.0.0                 # 轻量标签
git tag -a v1.0.0 -m "发布说明"  # 附注标签 (推荐)
git push origin v1.0.0         # 推送标签到远程

13.3 cherry-pick:摘取单个提交

只要某个分支的某一次提交,不要整个合并:

Bash
git cherry-pick a1b2c3d

13.4 reflog:查找引用的历史位置

reflog 会记录本地引用曾经指向的位置,可用于定位误操作前的提交:

Bash
git reflog                         # 查看 HEAD 的本地变动记录
git show HEAD@{2}                  # 先检查目标内容
git branch recovery HEAD@{2}      # 创建恢复分支,不覆盖当前工作区

reflog 是本地线索,不是备份

可达 reflog 条目默认保留时间通常为 90 天,不可达条目通常为 30 天,具体还受配置和垃圾回收影响。它不记录从未提交的内容,也不会同步到远程;重要工作仍需及时提交并备份。

十四、TODO 清单

  • 熟练使用 git status 与两种 git diff 判断改动所在区域
  • 尝试使用 git add -p 组织一条内容聚焦的提交
  • 了解创建分支、合并分支并说明冲突标记的含义
  • 了解 restorerevertresetreflog 的用途
  • 推送前检查提交历史、远程地址和待推送分支
  • 尝试创建或阅读一个项目的 .gitignore 并识别不该提交的敏感文件

十五、值得我们思考的问题

rebase 和 merge 有什么区别?什么时候使用?

merge 保留分支结构并产生合并提交,历史如实记录分支何时汇合。

rebase 把提交重新应用到目标分支顶端,得到线性历史,但会生成新的提交哈希。

判断依据主要是历史是否已被共享:已经推送到远程或多人协作的分支,优先使用 merge 而不是改写历史。

尚未推送的本地分支,可以根据项目约定选择,想要整洁的线性历史时用 rebase。冲突处理上 merge 一次解决,rebase 可能需要在重放的每个提交上逐个解决。具体取舍仍以项目约定为准。

十六、值得我们进一步阅读的资料

十七、值得我们观看的视频

Bilibili 合集「从零开始深入 git」(UP 主:寰宇牛奶unimilk,合集链接),按照视频标题与本节知识结构列出对应关系:

期数主题对应本节章节
01Git 设计理念与版本控制历史一、版本控制;二、Git 的核心概念
02Git 安装与配置、三层配置文件三、初始配置
03仓库基本使用、提交、文件更改与暂存四、基本工作流;六、选择性暂存
04IDE 中使用 Git、提交信息规范五、查看历史与差异;七、提交信息规范
05多分支开发:Branching 与 DAG二、Git 的核心概念;九、分支操作
06多分支开发:Merging、冲突、three-way 与 fast-forward九、分支操作;十、解决冲突
07Git Workflow、语义化版本号、分支模型九、分支操作;十三、常用技巧
08Rebase:用 merge 或变基跟进分支九、分支操作
09amend、stash、detached HEAD、gitignore、revert、reset八、撤销与回退;十二、忽略文件;十三、常用技巧
10Remote、clone、简单推拉与协作准备四、基本工作流;十一、远程仓库操作
11fetch、push、pull、force push、force-with-lease十一、远程仓库操作
12小团队、企业与大型开源项目的协作 Workflow十一、远程仓库操作;十五、值得我们思考的问题
13-AGitHub 账户授权、分支保护、PR、Issue、Releases1.16 代码托管平台
13-B参与大型开源项目1.16 代码托管平台

本页目录

讨论区

欢迎分享你的想法与建议