Git版本管理说明
面向零基础到进阶的 Git 实战手册,用「写作文/存档」的通俗类比讲解核心概念。覆盖从环境搭建、日常提交、分支管理、远程协作,到撤销回滚、高级技巧(rebase、cherry-pick 等)及团队工作流的完整知识链,每个命令均附具体场景、示例与避坑提醒,适合作为随时翻阅的操作速查指南。
前置必看:Git 核心概念
1. 什么是版本控制
版本控制系统(VCS)是用来记录文件内容变化、方便后续查阅特定版本、回溯历史、多人协作的工具。它主要解决三类痛点:
- 历史回溯:写错了代码想回到上个版本,不用手动备份一堆“最终版_v1_v2_最终版”文件;
- 多人协作:多个人同时改同一个项目,不会互相覆盖修改,能自动合并不同人的改动;
- 责任追溯:某行代码是谁改的、什么时候改的、为什么改,都能查到完整记录。
版本控制系统经历了三代演进:
- 本地版本控制:用本地数据库记录文件差异,比如 RCS,只能自己用,没法协作;
- 集中式版本控制(CVCS):比如 SVN、CVS,所有版本数据都存在中央服务器,所有人都从服务器拉取代码、提交修改。缺点是单点故障,服务器挂了所有人都没法工作,离线不能提交;
- 分布式版本控制(DVCS):比如 Git,每个人的本地都有完整的版本仓库,不依赖中央服务器,离线也能正常提交,服务器只是用来方便同步大家的改动,挂了也不影响本地工作。
2. Git 四大工作区域
Git 的工作流程围绕四个核心区域流转,理解了它们就理解了 Git 一半的逻辑:
| 区域 | 通俗类比 | 作用说明 |
|---|---|---|
| 工作区(Working Directory) | 你正在写的草稿纸 | 电脑里能直接看到、编辑的项目文件,我们日常写代码都在这个区域 |
| 暂存区(Staging Area / Index) | 待定稿文件夹 | 临时存放下次要提交的改动,相当于提交前的确认区,可以分批挑选要提交的内容 |
| 本地仓库(Local Repository) | 你的存档柜 | 所有提交过的历史版本都永久保存在这里,数据存在本地 .git 目录下 |
| 远程仓库(Remote Repository) | 云端备份网盘 | 托管在网络服务器上的仓库,比如 GitHub、Gitee、GitLab,用来多人共享、异地备份 |
3. 文件的四种状态
在 Git 眼中,项目里的文件永远处于以下四种状态之一,通过 git status 可以随时查看:
- 未跟踪(Untracked):新创建的文件,Git 还没认识它,不在版本控制范围内;
- 已修改(Modified):文件被改了,但还没放进暂存区;
- 已暂存(Staged):文件已经放进暂存区,下次提交就会被记录进版本历史;
- 已提交(Committed):文件已经被永久保存到本地仓库里。
文件状态的完整流转路径:
新建文件 → 未跟踪 →
git add→ 已暂存 →git commit→ 已提交 已提交的文件 → 修改 → 已修改 →git add→ 已暂存 →git commit→ 新版本提交
4. 分支的本质:指向提交的指针
很多新手觉得分支很复杂,其实 Git 的分支非常轻量,它本质上就是一个指向某次提交(commit)的可变指针。
- 主分支(main/master)就是一个叫 main 的指针,指向最新的提交;
- 新建一个分支,就是创建了一个新的指针,指向当前的提交;
- 切换分支,就是把 HEAD 指针指向对应的分支,同时把工作区的文件更新成分支对应的版本;
- 提交代码,就是当前分支的指针向前移动,指向新的提交。
这也是为什么 Git 创建、切换分支速度极快——只是修改一个几十字节的指针文件,和项目大小完全无关,这也是 Git 对比 SVN 等集中式工具的核心优势之一。
5. HEAD 指针
HEAD 是一个特殊的指针,它永远指向你当前所在的位置。
- 正常情况下,HEAD 指向当前分支,比如你在 main 分支,HEAD 就指向 main,间接指向最新的提交;
- 如果你直接切换到某个历史提交、某个标签,HEAD 就会直接指向那个提交,这种状态叫分离头指针(Detached HEAD)。这个状态下提交的代码没有分支指向它,切换分支后很容易丢失,是新手常见的坑。
第一篇:环境搭建与入门配置
1. 全平台安装教程
Windows 平台
- 去 Git 官网 下载对应系统位数的安装包;
- 运行安装程序,关键选项说明:
- 组件选择:默认勾选 Git Bash、Git GUI,建议勾选“Add a Git Bash Profile to Windows Terminal”;
- PATH 环境:选择“Git from the command line and also from 3rd-party software”,这样可以在 CMD、PowerShell 里直接用 git 命令;
- 默认编辑器:可以选 VS Code、Vim 等,根据自己习惯选择;
- 换行符配置:选择“Checkout Windows-style, commit Unix-style line endings”,解决跨平台换行符混乱问题;
- 终端选择:推荐用 MinTTY,比 Windows 自带的 CMD 好用;
- 安装完成后,打开 Git Bash,输入
git --version,出现版本号就是安装成功。
macOS 平台
有三种常用安装方式:
- Xcode 命令行工具:终端输入
git --version,如果没安装会自动弹出提示,跟着引导安装即可,最省心; - Homebrew 安装:先装 Homebrew,然后终端执行
brew install git,适合习惯包管理的用户; - 官网安装包:去 Git 官网下载 dmg 安装包手动安装。
验证:终端输入 git --version,输出版本号即成功。
Linux 平台
不同发行版用对应的包管理器安装即可:
- Debian / Ubuntu:
sudo apt update && sudo apt install git - CentOS / RHEL:
sudo yum install git - Arch Linux:
sudo pacman -S git
2. 首次全局配置(必做)
安装完 Git 第一件事就是配置用户名和邮箱,这是你提交代码的身份标识,每次提交都会带上这个信息。
Git 的配置有三个层级,优先级从高到低:
- 仓库级(local):只对当前仓库生效,配置存在项目
.git/config里; - 用户级(global):对当前操作系统用户的所有仓库生效,配置存在用户目录
~/.gitconfig里; - 系统级(system):对整个系统所有用户生效,配置存在 Git 安装目录下。
日常用用户级(global)配置即可,不同项目想单独配置就用仓库级。
基础身份配置
# 配置用户名,随便起,团队里用来识别是谁提交的
git config --global user.name "你的名字"
# 配置邮箱,建议和 GitHub/Gitee 账号邮箱保持一致
git config --global user.email "your_email@example.com"常用优化配置
# 1. 把默认主分支名改成 main(现在主流都用 main 代替 master)
git config --global init.defaultBranch main
# 2. 解决中文文件名乱码问题
git config --global core.quotepath false
# 3. 换行符统一配置
# Mac/Linux 用户:提交时转成 LF,检出时不转换
git config --global core.autocrlf input
# Windows 用户:检出时转 CRLF,提交时转 LF
git config --global core.autocrlf true
# 4. 设置默认编辑器为 VS Code(可选,默认是 Vim)
git config --global core.editor "code --wait"
# 5. 拉取默认用 rebase 模式,避免多余的合并提交(后面会详细讲)
git config --global pull.rebase true查看配置
# 查看所有生效的配置
git config --list
# 查看当前用户名
git config user.name
# 查看配置来源和详情
git config --list --show-origin3. SSH 密钥配置(远程协作必备)
和远程仓库通信有两种协议:HTTPS 和 SSH。HTTPS 每次推送都要输账号密码,比较麻烦;SSH 配置一次密钥后就能免密通信,更安全方便,是推荐方式。
生成 SSH 密钥
# 生成 ed25519 类型的密钥(比传统 RSA 更安全高效,推荐)
# -C 后面跟你的邮箱,作为备注
ssh-keygen -t ed25519 -C "your_email@example.com"执行后一路回车即可,默认会在用户目录的 .ssh 文件夹下生成两个文件:
id_ed25519:私钥,绝对不能泄露给别人;id_ed25519.pub:公钥,要配置到远程平台上。
如果需要兼容老系统,也可以用 RSA 密钥:ssh-keygen -t rsa -b 4096 -C "邮箱"
添加公钥到远程平台
以 GitHub 为例:
- 复制公钥内容:打开
~/.ssh/id_ed25ssh/id_ed25519.pub文件,全选复制所有内容;- Windows:可以用
cat ~/.ssh/id_ed25519.pub | clip一键复制到剪贴板; - Mac:
pbcopy < ~/.ssh/id_ed25519.pub
- Windows:可以用
- 登录 GitHub,进入 Settings → SSH and GPG keys → New SSH key;
- Title 随便填,比如“我的工作电脑”,Key 里粘贴刚才复制的公钥内容,点击 Add key。
Gitee、GitLab 等平台的配置方式完全一致。
测试连接
ssh -T git@github.com第一次连接会提示是否确认,输入 yes 回车,如果出现 Hi 你的用户名! You've successfully authenticated... 就说明配置成功了。
多账号多密钥配置
如果你同时用 GitHub 和公司内网 GitLab,需要配置多个密钥,可以通过 ~/.ssh/config 文件来区分:
# GitHub 个人账号
Host github.com
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519_github
# 公司 GitLab
Host gitlab.company.com
HostName gitlab.company.com
User git
IdentityFile ~/.ssh/id_ed25519_company4. 两种项目起步方式
方式一:本地全新项目初始化
如果你本地已经有项目文件,或者从零开始新建项目,用 git init 初始化:
# 进入你的项目文件夹
cd my-project
# 初始化 Git 仓库,执行后会生成一个 .git 隐藏目录
git init类比:买了个新本子,写上封面,正式开始用来写作文存档。
方式二:克隆远程已有项目
如果远程平台已经有现成的仓库,用 git clone 下载到本地:
# 克隆仓库,默认下载所有分支和完整历史
git clone https://github.com/username/repo.git
# SSH 协议地址
git clone git@github.com:username/repo.git常用进阶参数:
--depth 1:浅克隆,只下载最新一个版本的代码,不下载完整历史,速度快,适合只需要用代码不需要看历史的场景;--branch 分支名:只克隆指定分支;--recursive:递归克隆子模块,如果项目里包含子仓库就需要加这个参数。
类比:把云盘里别人共享的作文本,完整下载一份到自己电脑里,所有历史版本都有。
第二篇:日常核心操作(90% 高频命令)
日常开发 90% 的时间,都在和工作区、暂存区、本地仓库打交道,这几个命令是最常用的,一定要熟练。
1. git status:查看当前状态
⭐ 新手好习惯:每操作两步就敲一次 git status,随时知道文件处于什么状态,心里有数。
# 完整状态输出,有详细说明
git status
# 精简模式,一行显示一个文件状态,适合熟手
git status -s精简模式的状态标记含义:
??:红色,未跟踪的新文件;M:红色,文件已修改,未暂存;M:绿色,文件已暂存;A:绿色,新添加到暂存区的文件;D:文件被删除了;R:文件被重命名了。
2. git add:添加到暂存区
把工作区的改动放进暂存区,为提交做准备。
# 添加单个文件
git add src/index.js
# 添加整个目录
git add src/
# 添加所有 .js 后缀的文件(通配符)
git add *.js
# 添加当前目录所有改动(最常用)
git add .进阶用法
# 只添加已经被 Git 跟踪的文件的修改,不包含新文件
git add -u
# 交互式暂存,一个个改动选择要不要暂存,适合改了很多东西想分批提交
git add -p注意
git add . 是把当前目录所有改动都加进暂存区,提交前建议用 git diff --staged 看一眼暂存区的内容,避免把调试代码、日志、敏感信息不小心提交进去。
3. git commit:提交到本地仓库
把暂存区的内容永久保存成一个版本,提交到本地仓库。每一次提交都会生成一个唯一的哈希 ID,对应一个项目快照,以后随时可以回到这个版本。
基础用法
# 提交并写提交说明,必须写,不写不让提交
git commit -m "这里写提交说明,描述这次改了什么"常用进阶参数
# 懒人命令:跳过 git add,直接提交所有已跟踪文件的修改
# 注意:新增的未跟踪文件不会被提交
git commit -am "修复首页按钮样式bug"
# 修改上一次提交
# 场景:刚提交完发现写错了提交信息,或者漏了个文件
# 原理:用新的提交替换掉上一个提交,会改变提交哈希
# ❗ 注意:已经推送到远程的提交绝对不要 amend,会导致历史不一致
git commit --amend -m "正确的提交信息"
# 空提交,没有任何文件改动也生成一个提交,一般用来触发 CI/CD 流水线
git commit --allow-empty -m "触发构建"✅ 提交信息规范:Conventional Commits
查看完整提交信息规范 →很多新手写提交信息很随意,“更新”、“修复bug”、“改了点东西”,过半个月自己都不知道这次提交干了啥。团队协作里规范的提交信息非常重要,业界通用的规范是 Conventional Commits,格式:
<类型>: <描述>常用类型:
feat: 新功能、新特性fix: 修复 bugdocs: 文档修改style: 代码格式调整,不影响逻辑(比如空格、格式化、分号)refactor: 代码重构,没有加新功能也没修 bugperf: 性能优化test: 测试相关,加测试用例chore: 构建工具、依赖配置、脚手架等改动revert: 回滚之前的提交
好的例子:
git commit -m "feat: 新增用户登录验证码功能"
git commit -m "fix: 修复移动端导航栏错位问题"
git commit -m "docs: 更新安装部署文档"坏的例子(绝对不要写):
git commit -m "更新"
git commit -m "asdf"
git commit -m "修复bug"原子提交原则
一次提交只做一件事,不要把多个不相关的功能、bug修复攒在一个大提交里。
- 好处:出问题容易回滚,排查 bug 方便定位,代码审查好理解;
- 反例:一次提交里既改了登录功能,又改了首页样式,还修了支付bug,想回滚其中一个都没法弄。
4. git diff:查看具体改动
精确对比两个区域的文件差异,看具体改了哪几行。
# 1. 对比【工作区】和【暂存区】的差异:改了还没 add 的内容
git diff
# 2. 对比【暂存区】和【上次提交】的差异:已经 add 还没 commit 的内容
git diff --staged
# 等价写法
git diff --cached
# 3. 对比【工作区】和【上次提交】的差异
git diff HEAD
# 4. 对比两个提交之间的差异
git diff 提交号1 提交号2
# 5. 对比两个分支的差异
git diff main dev
# 6. 只看某个文件的差异
git diff src/index.js
# 7. 单词级别的对比,默认是行级
git diff --word-diff输出解读:
-开头的红色行:删除的内容;+开头的绿色行:新增的内容;- 没有标记的行:上下文,没改动。
5. git rm / git mv:删除与重命名
删除文件
# 同时删除工作区和暂存区的文件,下次提交就记录删除操作
git rm 文件名
# 只从暂存区和 Git 跟踪里删除,本地文件保留
# 场景:不小心把不该跟踪的文件 add 了,想取消跟踪但保留本地文件
git rm --cached 文件名注意:不要直接在文件夹里删文件,尽量用
git rm,否则 Git 会认为文件“失踪了”,状态会显示为 deleted 但未暂存。
重命名/移动文件
# 重命名文件,同时记录到暂存区
git mv 旧文件名 新文件名
# 移动文件到其他目录
git mv 文件名 目录/git mv 本质上等价于先复制文件、删除旧文件、添加新文件,但 Git 能识别出这是重命名操作,历史记录可以跟着文件走。
6. git log:查看提交历史
# 完整输出,显示每个提交的哈希、作者、时间、提交信息
git log
# 精简模式,一行显示一个提交,最常用
git log --oneline
# 图形化显示分支合并历史,直观看到分支分叉和合并
git log --graph --oneline --all
# 显示每个提交改动的文件统计
git log --stat
# 显示每个提交的具体代码改动
git log -p
# 只看某个作者的提交
git log --author="张三"
# 按关键词搜索提交信息
git log --grep="登录"
# 只看某个文件的修改历史
git log --oneline 文件名
# 看最近 n 条提交
git log -n 5美化别名配置
可以配置一个漂亮的 git lg 别名,彩色带时间、作者、分支图,比默认的好用很多:
git config --global alias.lg "log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit"配置完直接敲 git lg 就能看到美观的提交历史。
7. .gitignore:忽略不需要跟踪的文件
有些文件不需要放进 Git 版本控制,比如依赖包、构建产物、日志、本地配置、系统临时文件等,用 .gitignore 文件告诉 Git 忽略它们。
语法规则
# 井号开头是注释
# 忽略单个文件
secret.key
# 忽略所有 .log 后缀的文件
*.log
# 忽略 node_modules 整个目录
node_modules/
# 忽略 dist 目录下所有内容
dist/*
# 取反:上面忽略了所有 .log,但这个文件除外
!important.log
# 只忽略根目录下的 .env 文件,不包含子目录里的
/.env
# 忽略所有目录下的 .DS_Store 文件(Mac 系统文件)
.DS_Store常用项目模板
不同项目有不同的忽略规则,不用自己从零写:
- Node.js 前端项目:忽略
node_modules/、dist/、.env、.DS_Store、*.log; - Python 项目:忽略
__pycache__/、*.pyc、.venv/; - Java 项目:忽略
target/、.class、.idea/; - IDE 配置:
.idea/、.vscode/一般建议忽略,每个人的配置不一样。
可以去 gitignore.io 输入你的技术栈,自动生成完整的忽略文件。
全局 gitignore
有些文件是所有项目都要忽略的,比如系统的 .DS_Store、Thumbs.db,可以配置全局忽略文件,不用每个项目都写一遍:
# 创建全局忽略文件
touch ~/.gitignore_global
# 配置 Git 应用这个全局文件
git config --global core.excludesfile ~/.gitignore_global然后把通用的忽略规则写进 ~/.gitignore_global 就行。
常见问题:.gitignore 不生效
原因:文件已经被 Git 跟踪了,之后再加忽略规则是没用的,Git 还会继续跟踪这个文件。 解决方法:把文件从 Git 的暂存区里移除,本地文件保留,然后提交:
# 移除单个文件的跟踪
git rm --cached 文件名
# 移除整个目录的跟踪
git rm --cached -r node_modules/
# 然后正常提交
git add .
git commit -m "chore: 更新 gitignore 规则,移除跟踪文件"临时忽略已跟踪文件的修改
场景:项目里的配置文件,你改了本地的参数,但不想提交,也不想影响远程的版本。
# 让 Git 忽略这个文件的修改,假装没看到
git update-index --assume-unchanged 文件名
# 取消忽略,恢复跟踪修改
git update-index --no-assume-unchanged 文件名第三篇:分支管理(Git 的灵魂功能)
分支是 Git 最强大的特性,也是和其他版本控制工具拉开差距的核心。有了分支,你可以并行开发多个功能、修复 bug,互不干扰,开发完再合并回去。
1. 分支基础命令大全
查看分支
# 查看本地所有分支,带 * 的是当前所在分支
git branch
# 查看远程所有分支
git branch -r
# 查看所有分支(本地+远程)
git branch -a
# 查看分支最后一次提交
git branch -v
# 查看分支的远程跟踪关系
git branch -vv
# 查看已经合并到当前分支的分支
git branch --merged
# 查看还没合并到当前分支的分支
git branch --no-merged创建分支
# 基于当前分支创建新分支,创建完不会自动切换过去
git branch feature-login
# 基于指定提交创建分支
git branch bugfix 提交哈希
# 基于标签创建分支
git branch hotfix v1.2.0切换分支
推荐用 git switch,语义更清晰,是 Git 2.23 版本新增的专门用来切换分支的命令,老版本用 git checkout。
# 切换到 main 分支
git switch main
# 老版本写法,效果一样
git checkout main
# 快速切换到上一个分支,非常实用,来回切换很方便
git switch -创建并立即切换
最常用的方式,创建完直接切过去开发:
# 创建 feature-pay 分支并马上切换过去
git switch -c feature-pay
# 老版本写法
git checkout -b feature-pay重命名分支
# 把当前分支改名为 new-name
git branch -m new-name
# 重命名指定分支
git branch -m old-name new-name删除分支
# 安全删除:如果分支还没合并,会提示你,防止误删
git branch -d feature-login
# 强制删除:不管合没合并,直接删掉
git branch -D feature-login
# 删除远程分支
git push origin --delete feature-login好习惯:功能分支合并完就及时删掉,保持分支列表干净,不然时间久了一堆废弃分支分不清。
2. 合并分支:git merge
把另一个分支的改动,合并到当前分支里。
基础操作
目标:把 feature-login 分支的内容合并到 main 分支
# 1. 先切换到目标分支(要合并到哪里,就先切到哪个分支)
git switch main
# 2. 执行合并
git merge feature-login三种合并模式
1. Fast-Forward(快进合并)
触发条件:目标分支从创建后没有新的提交,当前分支一直往前走。 原理:Git 只是简单地把分支指针向前移动,不会生成新的提交,历史是一条直线。 特点:干净,但是看不出曾经有过这个分支,看不出功能是在哪合并的。
2. No-FF(不使用快进)
参数:git merge --no-ff 分支名 原理:强制生成一个新的合并提交,哪怕可以快进合并。 特点:保留了分支的历史痕迹,能看出来这个功能是在哪个提交合并的,团队协作更推荐用这种方式,历史更清晰。
3. Squash(压缩合并)
参数:git merge --squash 分支名 原理:把目标分支上所有的提交,压缩成一个新的提交,然后合并到当前分支,不会保留原分支的提交历史。 特点:主分支历史特别干净,一个功能对应一个提交;但丢失了分支里的详细提交记录,不利于排查。适合小功能分支,合并完主分支很清爽。
撤销合并
如果合并到一半冲突太多,不想合了,可以完全撤销合并,回到合并前的状态:
git merge --abort3. 合并冲突完全指南
冲突什么时候会出现
两个分支修改了同一个文件的同一行代码,或者一个删了文件一个改了文件,Git 不知道该保留哪个版本,就会报冲突,需要人工手动解决。
二进制文件(比如图片、压缩包)冲突更麻烦,没法逐行合并,只能选保留其中一个版本。
冲突标记解读
出现冲突后,有冲突的文件会被加上特殊标记:
<<<<<<< HEAD
首页标题:我的个人博客
=======
首页标题:技术分享站
>>>>>>> feature-login<<<<<<< HEAD到=======:当前分支的内容;=======到>>>>>>> 分支名:要合并的分支的内容;- 你需要决定最终保留什么,删掉所有标记行,保存文件。
解决冲突的标准步骤
- 执行
git status找到标红的冲突文件; - 打开冲突文件,找到
<<<<<<<标记的位置; - 编辑文件,决定最终保留的内容,删除所有冲突标记;
- 保存文件,执行
git add 冲突文件名,把解决后的文件放进暂存区; - 执行
git commit完成合并,提交信息会自动生成为合并提交。
冲突解决工具
手动改文件比较麻烦,可以用可视化工具:
- VS Code:内置冲突编辑器,会标红冲突区域,有四个按钮“接受当前更改”、“接受传入更改”、“接受双方更改”、“比较差异”,点按钮就能解决,非常方便;
- Vimdiff:命令行自带的对比工具;
- Beyond Compare:专业的对比工具,功能强大。
配置默认合并工具:
# 配置 VS Code 为默认 diff 和 merge 工具
git config --global diff.tool vscode
git config --global merge.tool vscode减少冲突的最佳实践
- 频繁同步主分支:每天把主分支的最新代码拉到自己的功能分支,不要等开发完才合并,攒得越久冲突越多;
- 小步提交:不要攒大提交,拆成小提交,合并冲突也好解决;
- 按模块分工:团队里尽量不同人改不同的文件,减少多人改同一处的概率;
- 提前沟通:要改公共的核心文件,提前和团队说一声,避免两个人同时大改。
4. 通用分支命名规范
统一的命名规范能让分支一目了然,不用点开就知道这个分支是干嘛的。推荐格式:类型/描述
| 分支类型 | 命名格式 | 例子 | 说明 |
|---|---|---|---|
| 主分支 | main / master | main | 生产环境稳定版本,受保护,不能直接提交 |
| 开发集成分支 | develop / dev | dev | 日常开发集成,所有功能分支合到这里 |
| 功能分支 | feature/功能描述 | feature/user-login | 新功能开发,从 dev 拉取 |
| Bug修复分支 | bugfix/问题描述 | bugfix/login-error | 普通bug修复,从 dev 拉取 |
| 紧急热修复 | hotfix/问题描述 | hotfix/pay-bug | 线上紧急bug,从 main 拉取 |
| 发布分支 | release/版本号 | release/v1.2.0 | 版本发布前的测试和准备 |
| 个人分支 | 用户名/描述 | zhangsan/test | 个人临时测试用,不建议推远程 |
命名用英文、小写,单词之间用连字符 - 分隔,不要用中文、空格、特殊字符。
第四篇:远程仓库协作
本地仓库只有自己能用,远程仓库是多人协作的枢纽,用来同步大家的代码、备份数据。常见的托管平台有 GitHub、Gitee、GitLab、Bitbucket 等。
1. git remote:远程仓库管理
origin 是什么?是远程仓库的默认别名,相当于给一长串仓库地址起个短名字,不用每次都输完整地址。一个本地仓库可以关联多个远程仓库。
# 查看当前关联的所有远程仓库别名
git remote
# 查看详情,显示别名对应的 fetch 和 push 地址
git remote -v
# 添加远程仓库
git remote add origin 仓库地址
# 修改远程仓库地址
git remote set-url origin 新地址
# 重命名远程仓库别名
git remote rename old-name new-name
# 删除远程仓库
git remote remove origin
# 查看某个远程仓库的详细信息
git remote show origin多远程仓库场景
比如想同时把代码推送到 GitHub 和 Gitee 两个平台,可以添加两个远程仓库:
# 添加 GitHub 为 origin
git remote add origin git@github.com:user/repo.git
# 添加 Gitee 为 gitee
git remote add gitee git@gitee.com:user/repo.git
# 推送到 GitHub
git push origin main
# 推送到 Gitee
git push gitee main2. git push:推送到远程
把本地仓库的提交,上传到远程仓库,更新远程分支。
基础用法
# 首次推送新分支,同时建立跟踪关系(-u = --set-upstream)
# 建立后以后直接 git push 就行,不用再指定分支
git push -u origin main
# 正常推送(已经绑定过跟踪关系的分支)
git push
# 推送所有本地分支到远程
git push --all origin
# 推送所有标签到远程
git push --tags
# 删除远程分支
git push origin --delete 分支名
# 删除远程标签
git push origin --delete 标签名⚠️ 强制推送:绝对红线
# 危险!强制覆盖远程分支,会删掉别人的提交
git push --force
# 相对安全的强制推送:只有远程分支没有新提交的时候才覆盖
# 改了自己个人分支的历史,推荐用这个
git push --force-with-lease铁则:
- 公共主分支(main、dev)绝对绝对不能强制推送,会毁掉所有人的提交历史,造成大事故;
- 只有你自己一个人用的功能分支,确认没人基于它开发,才能谨慎使用强制推送;
- 一般只有修改了本地提交历史(rebase、amend)之后,才需要强制推送。
推送被拒绝的常见原因
- 远程有新提交,本地不是最新:最常见,提示
non-fast-forward,先git pull拉取最新代码,解决冲突,再推送; - 没有权限:账号没有这个仓库的推送权限,找管理员开权限;
- 分支受保护:主分支设置了保护,不能直接推送,需要提 PR 合并;
- 本地历史和远程不一致:比如你 reset 了本地分支,和远程分叉了,需要强制推送(仅限个人分支)。
3. git fetch:获取远程更新
把远程仓库的所有最新提交、分支、标签,下载到本地,更新远程跟踪分支(origin/main 这种),但完全不会修改你的工作区和当前分支,非常安全。
# 获取 origin 远程所有更新
git fetch origin
# 只获取指定分支的更新
git fetch origin main
# 获取所有远程仓库的更新
git fetch --all
# 清理远程已经删除的本地跟踪分支
git fetch --prunefetch 之后你可以做什么:
- 查看远程更新了什么:
git log origin/main - 对比本地和远程的差异:
git diff main origin/main - 手动合并到本地分支:
git merge origin/main - 变基同步:
git rebase origin/main
理解 pull 和 fetch 的区别:
git pull = git fetch + git merge,pull 是一步到位自动合并,fetch 是先下载,你自己决定怎么合并。新手推荐多用 fetch,更可控,不容易出问题。
4. git pull:拉取并合并
下载远程最新代码,然后自动合并到当前分支。
基础用法
# 拉取当前跟踪分支的更新,自动合并
git pull
# 指定远程和分支
git pull origin main两种拉取模式
1. merge 模式(默认)
拉取后如果有分叉,会生成一个合并提交,历史线会有分叉,出现很多“Merge branch ‘main’ of …”这种无意义的合并提交,历史比较乱。
2. rebase 模式
参数:git pull --rebase 原理:拉取后把本地的提交,一个个“搬运”到远程最新提交的后面,历史是一条干净的直线,没有多余的合并提交。
团队协作强烈推荐用 rebase 模式拉取,历史干净清爽,好排查问题。可以配置全局默认用 rebase:
git config --global pull.rebase true常见报错:无关历史合并
报错:fatal: refusing to merge unrelated histories 原因:两个仓库没有共同的提交历史,比如本地 git init 的仓库,和远程新建的空仓库关联,第一次 pull 就会报这个错。 解决:加参数允许合并不相关历史:
git pull origin main --allow-unrelated-histories5. 标签 Tag 管理
标签就是给某个提交打个标记,比如版本号 v1.0.0,方便以后快速定位到这个版本,一般用于发布正式版本。
和分支的区别:分支指针会随着提交往前移动,标签打了之后就固定在那个提交上,永远不会动。
两种标签类型
1. 轻量标签(Lightweight)
本质就是一个指向提交的指针,没有额外信息,相当于给提交哈希起个别名。
git tag v1.0.02. 附注标签(Annotated)
是一个独立的 Git 对象,包含标签创建者、时间、标签说明,还可以 GPG 签名验证,正式发布推荐用这种。
# -a 指定是附注标签,-m 是标签说明
git tag -a v1.2.0 -m "正式发布 v1.2.0 版本,新增支付功能"标签操作
# 查看所有标签
git tag
# 模糊搜索标签
git tag -l "v1.*"
# 查看标签详情和对应的提交
git show v1.0.0
# 给历史提交打标签
git tag -a v0.9.0 提交哈希 -m "内测版本"
# 删除本地标签
git tag -d v1.0.0
# 推送单个标签到远程
git push origin v1.0.0
# 推送所有本地标签到远程
git push --tags
# 删除远程标签
git push origin --delete v1.0.0语义化版本规范(SemVer)
业界通用的版本号规则:主版本号.次版本号.修订号
- 主版本号(MAJOR):不兼容的 API 改动,大版本升级;
- 次版本号(MINOR):向下兼容的功能性新增,加新功能;
- 修订号(PATCH):向下兼容的问题修正,修 bug。
例子:v1.2.3 → 第1个主版本,第2个次版本,第3个修订版。 预发布版本可以加后缀:v1.0.0-alpha、v1.0.0-beta。
明白了,需要做两个调整:
- 章节编号:中文数字“二、三、四、五、六、七、八”改成阿拉伯数字“2. 3. 4. 5. 6. 7. 8.”,作为第五篇的下一级标题。
- 正文层级:
1.1、1.2等子标题下面的“核心原理”“适用场景”“效果总结”等小标题,改为正文加粗,不再作为子标题。
以下是修改后的完整文档:
第五篇:撤销与回滚大全(按场景查表)
很多新手学Git最头疼的就是「撤销操作」:改乱了代码想撤回、提交错了想回退、不小心删了分支想找回……不同的阶段对应完全不同的命令,用错了轻则代码混乱,重则永久丢失代码。
所有撤销操作的本质,都是对「工作区、暂存区、本地仓库」三大区域做不同程度的回退。理解了三大区域的流转逻辑,就能彻底搞懂所有撤销命令,不用死记硬背。
0. 前置说明:危险等级与核心原则
危险等级划分
| 等级 | 标识 | 说明 |
|---|---|---|
| 安全级 | 🟢 | 完全不会丢失代码,放心使用 |
| 注意级 | 🟡 | 操作不当可能丢失代码,确认后再执行 |
| 危险级 | 🔴 | 会永久删除代码/修改公共历史,非必要不用 |
核心操作原则
- 公共分支绝不重写历史:已经推送到团队共享的
main/dev分支,永远不要用reset/rebase等修改历史的命令后强制推送,优先用git revert。 - 危险操作留后路:拿不准的回退操作,先打个标签
git tag backup或者新建备份分支,出问题可以一键恢复。 - 先看状态再操作:执行任何撤销命令前,先敲
git status确认当前文件状态,避免误操作。
1. 核心命令深度解析:git reset 三种模式全演示
git reset是Git中最核心的回退命令,也是最容易搞混的命令。它的核心作用是移动当前分支的指针到指定提交,同时根据模式不同,选择性更新暂存区和工作区。
三种模式的本质区别,只在于「移动分支指针之后,要不要同步更新暂存区、要不要覆盖工作区」。
1.1 统一实验环境准备
我们先搭建一个完全一致的测试仓库,用同一个初始状态分别演示三种模式,你可以跟着敲命令直观感受差异。
步骤1:初始化测试仓库
# 创建空文件夹并进入
mkdir git-reset-test && cd git-reset-test
# 初始化Git仓库
git init
# 配置用户名邮箱(如果没配置过全局的话)
git config user.name "测试用户"
git config user.email "test@example.com"步骤2:生成3次提交,构建提交链
我们每次修改同一个文件demo.txt,做3次提交,形成 C1 → C2 → C3 的提交链,当前HEAD指向最新的C3。
# 第一次提交 C1
echo "第1行:初始内容" > demo.txt
git add .
git commit -m "C1:第一次提交"
# 第二次提交 C2
echo "第2行:新增内容" >> demo.txt
git add .
git commit -m "C2:第二次提交"
# 第三次提交 C3
echo "第3行:最新内容" >> demo.txt
git add .
git commit -m "C3:第三次提交"步骤3:验证初始状态
# 查看提交历史(一行显示)
git log --oneline你会看到类似输出(哈希值随机,顺序从新到旧):
xxxxxxx (HEAD -> main) C3:第三次提交
xxxxxxx C2:第二次提交
xxxxxxx C1:第一次提交查看当前工作区文件内容:
cat demo.txt输出:
第1行:初始内容
第2行:新增内容
第3行:最新内容此时三个区域的状态是完全一致的:
- 本地仓库:C3提交的完整内容
- 暂存区:和C3完全一致
- 工作区:和C3完全一致
接下来我们所有演示,都是从C3回退到C2(也就是HEAD~1,上一个提交),观察三种模式下三个区域的不同变化。
1.2 模式一:—soft 软重置(最温和,只动仓库)
核心原理:只移动当前分支指针到目标提交,暂存区和工作区完全保持不变,不会有任何修改。相当于只撤销了git commit操作,git add的内容还完整保留在暂存区。
危险等级:🟢 完全安全,不会丢失任何代码。
适用场景:
- 刚提交完发现提交信息写错了,想撤回重写;
- 提交完发现漏加了一个文件,想撤回来补上再一起提交;
- 想把连续的几个小提交合并成一个大提交。
完整操作演示:
- 执行回退命令
git reset --soft HEAD~1- 验证1:提交历史变化
git log --oneline输出:
xxxxxxx (HEAD -> main) C2:第二次提交
xxxxxxx C1:第一次提交✅ C3提交从历史里消失了,main指针移动到了C2。
- 验证2:暂存区状态
git status输出:
On branch main
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: demo.txt✅ C3提交的所有修改,还完整留在暂存区里,处于「已暂存,未提交」的状态。
- 验证3:工作区内容
cat demo.txt输出还是三行完整内容:
第1行:初始内容
第2行:新增内容
第3行:最新内容✅ 工作区没有任何变化,C3的修改完整保留。
效果总结:git reset --soft 相当于只撤销了commit操作,回到了「刚执行完git add,还没git commit」的状态。你可以修改提交信息、补充文件,然后重新git commit即可。
实战场景:修改写错的提交信息
# 刚提交完发现信息写错了
git commit -m "feat:新增登功能" # 少打了个"录"字
# 软撤回上一个提交
git reset --soft HEAD~1
# 重新提交,写正确的信息
git commit -m "feat:新增登录功能"更简单的方式是直接用
git commit --amend -m "正确信息",本质上就是reset --soft + 重新commit的封装。
1.3 模式二:—mixed 混合重置(默认模式,最常用)
核心原理:移动分支指针到目标提交,清空暂存区(恢复成目标提交的状态),但工作区的修改完全保留。相当于同时撤销了git commit和git add操作,修改还留在工作区,你可以重新编辑后再提交。
注意:这是
git reset的默认模式,不加任何参数的时候,自动就是--mixed模式。
危险等级:🟢 安全,不会丢失代码,所有修改都保留在工作区。
适用场景:
- 提交的内容有错误,想撤回来修改完再重新提交;
- 一次性add了太多文件,想拆分成多个提交分批提交;
- 不小心add了不该提交的文件,想从暂存区撤出来。
完整操作演示:
我们先把仓库恢复到初始的C3状态,再做演示:
# 重新提交C3,回到初始状态
echo "第3行:最新内容" >> demo.txt
git add .
git commit -m "C3:第三次提交"- 执行回退命令
# 不加参数,默认就是--mixed模式
git reset HEAD~1
# 等价于 git reset --mixed HEAD~1- 验证1:提交历史变化
git log --oneline输出:
xxxxxxx (HEAD -> main) C2:第二次提交
xxxxxxx C1:第一次提交✅ 和soft模式一样,C3提交从历史里消失,main指针移动到C2。
- 验证2:暂存区状态
git status输出:
On branch main
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: demo.txt✅ 暂存区被清空了,C3的修改从暂存区撤回到了工作区,处于「已修改,未暂存」的状态。
- 验证3:工作区内容
cat demo.txt输出还是三行完整内容:
第1行:初始内容
第2行:新增内容
第3行:最新内容✅ 和soft模式一样,工作区的修改完整保留,没有任何变化。
效果总结:git reset --mixed 相当于撤销了commit和add两步操作,回到了「改完代码,还没执行git add」的状态。你可以继续修改代码,调整好了再重新git add + git commit。
这是日常开发最常用的reset模式,既可以撤回提交,又不会丢失任何代码修改,非常灵活。
实战场景:拆分一次性提交的多个功能
你不小心把A、B两个功能的代码一次性提交了,想拆成两个独立提交:
# 撤回上一次提交,修改保留在工作区
git reset HEAD~1
# 先add功能A的文件,提交
git add feature-a.js
git commit -m "feat:实现A功能"
# 再add功能B的文件,提交
git add feature-b.js
git commit -m "feat:实现B功能"1.4 模式三:—hard 硬重置(最危险,全量覆盖)
核心原理:移动分支指针到目标提交,清空暂存区,同时直接覆盖工作区的所有文件,三个区域完全同步到目标提交的状态。目标提交之后的所有修改,无论是暂存区还是工作区的,都会被彻底删除,无法找回(除非你已经提交过)。
危险等级:🔴 高危操作!会永久删除未提交的代码,执行前务必确认。
适用场景:
- 某个提交的代码完全写错了,彻底不想要了,想干净利落地回到某个版本;
- 本地代码改得一团糟,想完全恢复成远程仓库的干净状态;
- 个人分支重写历史后,强制同步本地状态。
完整操作演示:
再次把仓库恢复到初始的C3状态:
echo "第3行:最新内容" >> demo.txt
git add .
git commit -m "C3:第三次提交"- 执行回退命令
git reset --hard HEAD~1- 验证1:提交历史变化
git log --oneline输出:
xxxxxxx (HEAD -> main) C2:第二次提交
xxxxxxx C1:第一次提交✅ 和前两种模式一样,C3提交从历史里消失,main指针移动到C2。
- 验证2:暂存区状态
git status输出:
On branch main
nothing to commit, working tree clean✅ 暂存区完全清空,和C2提交状态一致。
- 验证3:工作区内容
cat demo.txt输出只有两行:
第1行:初始内容
第2行:新增内容✅ 工作区被完全覆盖了!C3新增的「第3行」彻底消失了,整个文件回到了C2提交时的状态。
效果总结:git reset --hard 是最彻底的回退,三个区域完全同步到目标提交的状态,所有之后的修改都会被删除。相当于彻底回到了过去的某个版本,就像后面的提交从来没发生过一样。
特别警告:
- 执行
--hard前,一定要确认工作区和暂存区里没有需要保留的代码,一旦执行就找不回来了(除非你已经提交过,可以用reflog找回)。 - 绝对不要在有未提交重要代码的仓库里随便执行
git reset --hard。
1.5 三种模式横向对比总表
一张表彻底分清三种模式的区别:
| 对比维度 | —soft 软重置 | —mixed 混合重置(默认) | —hard 硬重置 |
|---|---|---|---|
| 移动分支指针 | ✅ 是 | ✅ 是 | ✅ 是 |
| 更新暂存区 | ❌ 不更新,保留原暂存内容 | ✅ 更新为目标提交状态(清空原有暂存) | ✅ 更新为目标提交状态 |
| 更新工作区 | ❌ 不更新,保留所有修改 | ❌ 不更新,保留所有修改 | ✅ 完全覆盖为目标提交状态 |
| 等价撤销操作 | 只撤销 git commit | 撤销 git commit + git add | 撤销所有修改,回到提交时状态 |
| 危险等级 | 🟢 安全 | 🟢 安全 | 🔴 高危 |
| 核心用途 | 修改提交信息、合并提交 | 撤回提交重新修改、拆分提交 | 彻底丢弃修改,回到历史版本 |
| 记忆口诀 | 软重置,只动仓库 | 混合重置,动仓库+暂存区 | 硬重置,全动,啥都不剩 |
1.6 reset 进阶用法
1.6.1 回退到任意指定提交
不只是回退上1个提交,你可以用提交哈希回退到任意历史版本:
# 先查看历史,找到目标提交的哈希
git log --oneline
# 回退到指定提交(写哈希的前7位就行,不用写全)
git reset --mixed a1b2c3d也可以用HEAD~n回退n个提交:
# 回退3个提交
git reset HEAD~31.6.2 路径级reset:只回退单个文件
绝大多数新手不知道:git reset 可以只针对某个文件/目录执行,不会影响整个仓库。
场景:你git add .一次性加了一堆文件,发现其中有个文件加错了,想把它单独撤出暂存区,其他文件保留。
# 只把 error.log 撤出暂存区,其他add的文件不受影响
git reset HEAD error.log
# 等价于 git restore --staged error.log路径级reset只有
--mixed模式有效,不能加--soft或--hard。
1.6.3 合并多个连续提交
用reset --soft可以轻松把连续的n个小提交合并成1个大提交,保持主分支历史干净:
# 把最近3个提交合并成1个
git reset --soft HEAD~3
# 重新提交,写一个统一的提交信息
git commit -m "feat:完整实现用户中心模块"1.7 reset 红线与避坑指南
公共分支绝对禁用reset+强推 已经推送到团队共享的
main/dev分支,不要用git reset回退然后git push --force强制覆盖。这会导致所有团队成员的本地历史和远程不一致,产生大量冲突,甚至丢失别人的提交。 公共分支想撤销提交,永远优先用git revert(后面会讲)。—hard 执行前必确认 敲
git reset --hard之前,先停一秒,确认:- 工作区有没有未提交的重要代码?
- 是不是真的完全不需要目标提交之后的所有内容? 拿不准就先打个备份标签:
git tag backup_before_reset,出问题直接恢复。
不要在分离头指针状态下随便reset 分离头指针(detached HEAD)状态下,HEAD不指向任何分支,reset之后提交很容易丢失。如果只是想查看历史版本,建议直接新建分支再操作。
2. 🟢 安全级撤销:只改工作区/暂存区,不碰提交历史
这类操作完全不会修改提交历史,只影响本地文件,是日常开发最高频的撤销场景。
2.1 场景1:工作区改乱了,还没add,想恢复原样
症状:文件改了一堆,越改越乱,还没执行git add,想回到修改之前的状态,也就是和暂存区/上一次提交保持一致。
命令:
# 恢复单个文件
git restore 文件名
# 恢复整个目录
git restore src/pages/
# 恢复所有修改的文件(慎用,会清空所有未add的修改)
git restore .老版本等价写法:Git 2.23之前没有git restore命令,用git checkout实现:
git checkout -- 文件名--的作用是明确表示后面跟的是文件名,避免和分支名冲突(比如有个文件叫main,和分支重名)。
原理:用暂存区里的版本覆盖工作区的文件;如果暂存区里没有这个文件(也就是修改后没add过),就用HEAD提交里的版本覆盖。
注意事项:🔴 这个操作会永久丢弃工作区的修改,没有办法找回,执行前确认清楚。
2.2 场景2:已经add了,想从暂存区撤回到工作区
症状:手滑把不该提交的文件add进了暂存区,想撤回来,但是保留工作区的修改,不要删掉代码。
命令:
# 撤出单个文件
git restore --staged 文件名
# 撤出所有文件
git restore --staged .等价写法:就是我们前面讲的路径级reset:
git reset HEAD 文件名原理:把暂存区里的文件恢复成HEAD提交的状态,工作区的修改完全保留。相当于撤销了git add操作。
注意事项:🟢 完全安全,不会丢失任何代码,只是把文件从暂存区拿回工作区而已。
3. 🟡 注意级撤销:修改本地提交历史(未推送远程)
这类操作会修改本地的提交历史,只要还没推送到远程仓库,就不会影响其他人,属于个人分支的常规操作。
3.1 场景3:刚提交完,发现提交信息写错了
最快方案:git commit --amend
# 直接修改上一次提交的信息
git commit --amend -m "正确的提交信息"原理:本质是git reset --soft HEAD~1 + 重新git commit的封装,用新的提交替换掉旧的提交,会改变提交哈希。
注意:
- 只能修改最近一次提交的信息。
- ❗ 已经推送到远程的提交不要用
--amend,会导致本地和远程历史不一致。
3.2 场景4:刚提交完,发现漏了个文件没加进去
操作步骤:
# 把漏掉的文件加入暂存区
git add 漏掉的文件.js
# 追加到上一次提交里,不会生成新的提交
git commit --amend --no-edit--no-edit表示沿用原来的提交信息,不用修改。如果想顺便改提交信息,去掉这个参数就行。
3.3 场景5:想修改很早之前的某一次提交
如果不是最近一次提交,而是历史中更早的提交,用交互式变基git rebase -i实现:
# 对最近5个提交进行交互式编辑
git rebase -i HEAD~5在弹出的编辑器里,把想修改的提交前面的pick改成edit,保存退出。 然后Git会暂停在那个提交,你修改代码后:
git add 修改的文件
git commit --amend --no-edit
git rebase --continue就能完成对历史提交的修改。
注意:这会重改该提交之后所有提交的哈希,已经推送远程的公共分支不要这么做。
4. 🟡 远程提交撤销:已经push到远程了怎么办
代码已经推送到远程仓库,就不能随便用reset重写历史了,分两种场景处理。
4.1 推荐方案:git revert 反向提交(安全,公共分支首选)
核心原理:不删除原来的提交,而是生成一个全新的提交,把目标提交的修改完全抵消掉。提交历史完整保留,不会造成任何人的历史冲突。
危险等级:🟡 安全,不修改原有历史,只新增提交。
适用场景:所有已经推送到远程的提交,尤其是公共分支上的提交,是团队协作的标准撤销方式。
操作演示:
# 1. 先找到要撤销的提交哈希
git log --oneline
# 2. 执行反向提交
git revert 目标提交哈希执行后会自动弹出编辑器,让你填写新提交的信息,默认是Revert "原提交信息",直接保存即可。
完成后会生成一个新的提交,内容正好和原提交相反:原提交加了的代码,revert就删掉;原提交删掉的代码,revert就加回来。
# 3. 推送到远程
git push特殊情况:撤销合并提交
如果要撤销的是一个合并提交(merge commit),需要加-m参数指定保留哪个父分支的代码:
# -m 1 表示保留第一个父提交(也就是合并时的目标分支,比如main)的代码
git revert -m 1 合并提交哈希优缺点:
- ✅ 优点:完全安全,不破坏历史,可追溯,不会影响团队其他人;
- ❌ 缺点:历史会多一条撤销记录,提交历史不是完全“干净”的。
4.2 危险方案:reset + force push(仅个人分支可用)
核心原理:本地用git reset回退版本,然后强制推送覆盖远程分支。
危险等级:🔴 高危!仅适用于只有你自己一个人用的个人功能分支。
适用场景:你自己的功能分支,还没人基于它开发,提交错了想彻底删掉,重新推送。
操作步骤:
# 1. 本地回退到目标版本
git reset --hard HEAD~1
# 2. 强制推送到远程(推荐用--force-with-lease,更安全)
git push --force-with-lease origin 分支名为什么推荐 —force-with-lease:普通的git push --force会无脑覆盖远程分支,如果在你操作期间,有别人往这个分支推了新提交,会被直接覆盖删掉。--force-with-lease会先检查:如果远程分支有你不知道的新提交,就会拒绝推送,不会覆盖别人的代码,是更安全的强制推送方式。
绝对禁令:❌ 绝对不要对main、dev等公共分支执行reset + force push,这是团队协作的红线,会造成严重的协作事故。
5. 🟢 临时储藏:git stash 代码写一半被打断怎么办
5.1 核心作用
开发到一半,突然要去修线上bug、切分支处理别的需求,当前代码还没到可以提交的程度,不想提交半成品。 这时候用git stash可以把工作区和暂存区的修改临时储藏起来,工作区瞬间变干净,处理完别的事情再回来恢复继续写。
5.2 常用命令全解
| 命令 | 作用 |
|---|---|
git stash / git stash push | 储藏当前所有已跟踪文件的修改 |
git stash save "备注信息" | 带备注的储藏,方便后续识别是啥内容 |
git stash list | 查看所有储藏记录,栈结构,最新的是stash@{0} |
git stash show stash@{n} | 查看第n条储藏的具体改动内容 |
git stash pop | 取出最近的储藏,应用到工作区,同时删除这条储藏 |
git stash apply stash@{n} | 取出指定的储藏,不删除记录,可以多次应用 |
git stash drop stash@{n} | 删除指定的储藏记录 |
git stash clear | 清空所有储藏记录 |
git stash -u | 同时储藏未跟踪的新文件(默认只储藏已跟踪的文件) |
git stash -a | 储藏所有文件,包括被.gitignore忽略的文件 |
5.3 标准工作流演示
# 正在feature分支写功能,突然接到线上bug需求
git stash save "用户中心功能开发到一半"
# 切换到主分支修bug
git switch main
# ... 修bug、提交、推送 ...
# 修完bug切回功能分支
git switch feature-user
# 恢复之前的储藏,继续开发
git stash pop5.4 常见问题
取出储藏时有冲突怎么办? 和合并冲突处理方式完全一样:手动解决冲突 →
git add冲突文件 → 完成。冲突解决后,stash记录不会自动删除,需要手动git stash drop删掉。想把储藏应用到别的分支? 先切换到目标分支,再执行
git stash pop即可,储藏可以在任意分支上恢复。
6. 🔴 救命神器:git reflog 误删代码/分支全找回
6.1 核心原理
很多人以为reset --hard删了代码、删了分支就找不回来了,其实Git几乎不会真的立刻删除数据。
Git有一个「引用日志」(reflog),会记录你本地所有HEAD指针的移动操作:提交、切换分支、reset、merge、rebase……每一次操作都会被记录下来,默认保留30天。 只要你的代码曾经提交过,哪怕提交被删了、分支被删了,都能在reflog里找到记录,然后恢复回来。
6.2 基本用法
# 查看所有操作历史
git reflog输出格式:
xxxxxxx (HEAD -> main) HEAD@{0}: reset: moving to HEAD~1
xxxxxxx HEAD@{1}: commit: C3:第三次提交
xxxxxxx HEAD@{2}: commit: C2:第二次提交
xxxxxxx HEAD@{3}: commit (initial): C1:第一次提交每一行都是一次操作,前面的哈希就是当时HEAD指向的提交哈希,HEAD@{n}是操作序号,数字越小越新。
6.3 实战场景1:误执行reset —hard,找回删掉的提交
症状:手滑执行了git reset --hard HEAD~1,把C3提交删掉了,现在想恢复回来。
恢复步骤:
- 查看操作历史,找到目标提交
git reflog找到reset之前的那次操作,比如HEAD@{1}对应的就是reset前的C3提交,记下它的哈希。
- 执行恢复 方案A(直接重置回去):
git reset --hard 目标提交哈希方案B(更安全,新建分支恢复):
git switch -c recovery-branch 目标提交哈希恢复完可以验证一下,代码完整回来了。
6.4 实战场景2:误删分支,恢复整个分支
症状:不小心git branch -D删掉了还没合并的功能分支,里面有写了一半的代码。
恢复步骤:
用reflog找到被删分支的最后一次提交哈希 删分支的操作也会被reflog记录下来,找到那个分支最后一次提交的哈希。
重建分支
git switch -c 恢复后的分支名 目标提交哈希整个分支就完整回来了,所有提交历史都在。
6.5 注意事项
- reflog是本地仓库独有的,只记录你本地的操作,不会同步到远程仓库。远程仓库的操作不会出现在你的本地reflog里。
- 默认保留30天,超过时间的记录可能会被Git垃圾回收清理掉。所以误操作了要尽快找,不要等太久。
- 只要代码曾经提交过,99%都能通过reflog找回来。出了问题不要慌,先敲
git reflog看看。
7. 撤销操作选型速查表
遇到问题直接查表,不用翻全文:
| 场景 | 推荐命令 | 危险等级 | 修改提交历史? | 会丢代码吗? | 适用阶段 |
|---|---|---|---|---|---|
| 工作区改乱了,想恢复原样 | git restore 文件名 | 🟢 | 否 | 未add的修改会丢失 | 修改后,add前 |
| add错了文件,想撤出暂存区 | git restore --staged 文件名 | 🟢 | 否 | 不会 | add后,commit前 |
| 刚提交完,想改提交信息 | git commit --amend -m "新信息" | 🟡 | 是(替换上一个提交) | 不会 | commit后,push前 |
| 刚提交完,想撤回来改内容 | git reset HEAD~1(默认—mixed) | 🟡 | 是 | 不会,修改保留在工作区 | commit后,push前 |
| 想合并连续的几个小提交 | git reset --soft HEAD~n + 重新提交 | 🟡 | 是 | 不会 | 本地未推送 |
| 彻底不想要某个提交的所有内容 | git reset --hard 提交号 | 🔴 | 是 | 会,目标提交之后的修改全删 | 本地未推送,确认无用 |
| 已经push到远程公共分支,想撤销 | git revert 提交号 | 🟡 | 否(新增提交) | 不会 | 推送后,公共分支 |
| 个人远程分支想彻底删提交 | git reset --hard + git push --force-with-lease | 🔴 | 是 | 可能覆盖别人代码 | 仅个人独享分支 |
| 代码写一半,临时要切分支 | git stash | 🟢 | 否 | 不会 | 任意未提交阶段 |
| 误删提交/分支,想找回 | git reflog + 恢复 | 🟢 | - | 能找回 | 任何误操作后 |
8. 新手避坑总结
- 先分阶段,再选命令:先想清楚你的修改在哪个区域(工作区/暂存区/已提交/已推送),再选对应的撤销命令,不要上来就瞎试。
- 公共分支永远用revert:只要是团队共享的分支,撤销提交永远优先用
git revert,不要重写历史。 - 危险操作留后路:拿不准的reset、rebase操作,先打个tag或者建个备份分支,出问题一键恢复。
- 丢了代码先找reflog:不要慌,Git比你想象的更安全,只要提交过,基本都能找回来。
- 多练多用:建个测试仓库随便折腾各种命令,踩过几次坑就彻底记住了,比死记硬背管用得多。
第六篇:高级进阶技巧
1. git rebase:变基
什么是变基
通俗说就是“换基准”,把你当前分支的所有提交,整体搬运到目标分支的最新提交后面。
- merge 是三方合并,生成新的合并提交,历史有分叉;
- rebase 是把提交一个个重新应用一遍,生成新的提交哈希,历史是一条直线。
基础用法:同步主分支代码
场景:你在功能分支开发,主分支更新了,想把主分支的最新代码同步过来。
# 1. 先切到你的功能分支
git switch feature-login
# 2. 拉取远程最新
git fetch origin
# 3. 变基到主分支
git rebase origin/main同步完你的分支提交都在主分支最新提交后面,历史是一条直线,比 merge 清爽很多。
变基过程中的冲突处理
变基过程中遇到冲突,rebase 会暂停,等你解决冲突:
- 手动解决冲突文件;
git add 冲突文件,把解决后的文件放进暂存区;- 执行
git rebase --continue,继续变基过程; - 如果中途想放弃,执行
git rebase --abort,完全回到变基前的状态。
注意:变基可能会遇到多次冲突,因为是一个个提交应用,每遇到冲突就停一次,解决完继续。
交互式变基:git rebase -i ⭐ 非常实用
批量编辑历史提交,可以修改提交信息、合并提交、删除提交、调整顺序、拆分提交。
# 对最近 3 个提交进行交互式操作
git rebase -i HEAD~3执行后会打开编辑器,每个提交前面有一个命令,默认是 pick,可以改成对应的命令:
pick / p:保留这个提交,不做修改;reword / r:保留提交内容,修改提交信息;edit / e:保留提交,但暂停,让你修改内容,可以补文件、改代码;squash / s:把这个提交合并到上一个提交里,保留两个的提交信息;fixup / f:和 squash 类似,但丢弃这个提交的信息,只保留内容;drop / d:删除这个提交,完全丢弃这个提交的内容;- 调整顺序:直接上下移动行就行。
实战场景1:合并多个零散提交
比如你开发的时候提交了很多“修复”、“再改”、“调整”的小提交,想合并成一个完整的提交再合并到主分支。 操作:把要合并的提交前面改成 squash,保存退出,然后编辑新的提交信息就行。
实战场景2:修改很早之前的提交信息
找到要改的提交,前面改成 reword,保存后会弹出编辑器让你改提交信息。
rebase 黄金铁则
永远不要对公共分支(main、dev 等大家都在用的分支)执行 rebase!
- 原因:rebase 会重写提交历史,生成新的提交哈希,其他人本地的历史和远程就不一致了,会造成大量冲突和混乱,甚至丢失代码。
- 适用范围:只在你自己一个人用的本地功能分支上用;如果已经推远程了,确认只有你自己用,才能 rebase 然后 force-with-lease 推送。
2. git cherry-pick:拣选提交
选择性地把某一个或几个提交,从一个分支搬运到当前分支,不用合并整个分支。
适用场景
- 线上分支的 bug 修复,需要同步到开发分支;
- 别人分支里有个提交你需要,但不想合并整个分支;
- 版本回退之后,想把其中某几个提交捡回来。
基础用法
# 拣选单个提交
git cherry-pick 提交哈希
# 拣选多个提交
git cherry-pick 提交1 提交2 提交3
# 拣选一段连续的提交(不包含起点,包含终点)
git cherry-pick 起点提交..终点提交
# 包含起点的写法
git cherry-pick 起点提交^..终点提交进阶参数
# -n / --no-commit:只把提交的内容放进暂存区,不自动生成新提交
# 适合需要修改一下再提交的场景
git cherry-pick -n 提交哈希
# -e / --edit:拣选后编辑提交信息
git cherry-pick -e 提交哈希冲突处理
和 rebase 一样:
- 解决冲突文件;
git add暂存;git cherry-pick --continue继续;- 放弃:
git cherry-pick --abort。
3. git bisect:二分查找 Bug
用二分法在提交历史里快速定位“哪个提交引入了 bug”,大大提升排查效率。适合不知道 bug 什么时候出现的场景,几十上百个提交一个个测太慢。
原理
你告诉 Git 一个“好版本”(没有 bug)和一个“坏版本”(有 bug),Git 自动切到中间的版本,你测试后告诉 Git 是好是坏,一步步缩小范围,最终定位到肇事提交。
操作步骤
# 1. 启动二分查找
git bisect start
# 2. 标记当前版本是坏的(有 bug)
git bisect bad
# 3. 标记一个已知的好版本(比如 v1.0.0 或者某个提交哈希)
git bisect good v1.0.0然后 Git 会自动切换到中间的提交,你测试这个版本有没有 bug:
- 有 bug:
git bisect bad - 没 bug:
git bisect good
重复几次,Git 就会输出第一个引入 bug 的提交哈希和信息,你就可以去看这个提交改了什么。
# 排查完退出二分查找,回到原来的分支
git bisect reset进阶:自动化测试
如果有单元测试脚本,可以让 Git 自动运行测试、自动标记好坏,完全不用手动测:
git bisect run ./test.shGit 会自动二分,自动跑脚本,直到找到第一个测试失败的提交。
4. git blame:追溯代码作者
查看文件的每一行代码,最后是谁修改的,在哪个提交里修改的,修改时间是什么。出了问题找负责人,或者想了解某段代码的背景时用。
# 查看整个文件的每一行作者
git blame 文件名
# 查看指定行范围,比如第 10 到 20 行
git blame -L 10,20 文件名
# 忽略空白字符的修改
git blame -w 文件名GUI 增强:GitLens
VS Code 的 GitLens 插件可以直接在行尾显示作者和提交时间,鼠标悬停还能看详情,非常方便,开发必备。
5. 子模块与子树
git submodule
在一个 Git 仓库里嵌套另一个 Git 仓库,主仓库记录子仓库的版本号。适合依赖公共组件、第三方库的场景。
# 添加子模块
git submodule add 子仓库地址 存放路径
# 克隆带子模块的仓库,需要递归克隆
git clone --recursive 主仓库地址
# 更新子模块到最新版本
cd 子模块目录
git pull
cd ..
git add 子模块目录
git commit -m "更新子模块版本"
# 删除子模块(步骤比较多)
git submodule deinit 子模块路径
git rm 子模块路径
rm -rf .git/modules/子模块路径缺点:操作比较繁琐,新手容易踩坑,团队协作成本高。
git subtree
另一种子仓库管理方式,把子仓库的内容作为目录放进主仓库,比 submodule 友好,普通成员感知不到子仓库的存在。 适合需要把外部项目代码合进来,又想保持简单的场景。
6. Git LFS:大文件存储
Git 不适合存大文件(图片、视频、模型、二进制包等),每次修改大文件 Git 都会保存完整版本,仓库会越来越大,克隆拉取越来越慢。
Git LFS(Large File Storage)就是解决这个问题的:用小的指针文件代替大文件,大文件本身存在 LFS 服务器上,拉取的时候只下载当前版本需要的大文件。
安装使用
- 安装 Git LFS 客户端;
- 仓库初始化:
git lfs install - 跟踪大文件类型:
git lfs track "*.psd"git lfs track "*.zip" - 正常 add、commit、push 就行,Git 自动处理。
历史大文件迁移
如果已经把大文件提交进 Git 了,可以用 git lfs migrate 工具把历史里的大文件迁移成 LFS,减小仓库体积。
7. Git Hooks:自动化钩子
Git 在特定事件(提交、推送、合并等)发生时,会自动触发执行自定义脚本,可以用来做代码检查、格式化、测试、提交信息校验等自动化工作。
分类
- 客户端钩子:本地执行,比如 pre-commit、commit-msg、pre-push、post-merge;
- 服务端钩子:远程仓库执行,比如 pre-receive、post-receive,一般 GitLab 平台用。
常用客户端钩子
- pre-commit:提交前执行,最常用。比如运行 ESLint 检查代码规范、Prettier 格式化、跑单元测试,不通过就阻止提交,保证代码质量。
- commit-msg:提交信息生成后执行,校验提交信息是否符合规范(比如 Conventional Commits)。
- pre-push:推送前执行,跑完整测试、构建检查,不通过不让推送。
- post-merge:合并完成后执行,比如自动安装依赖。
钩子存放位置
默认在仓库的 .git/hooks 目录下,有很多 .sample 结尾的示例文件,去掉 .sample 后缀就会生效。
团队共享钩子
默认 .git/hooks 里的钩子不会被提交到仓库,团队共享比较麻烦。推荐用工具管理:
- Husky:前端项目最常用,配置简单,可以把钩子配置提交到仓库,所有人安装依赖后自动生效;
- pre-commit:多语言通用的钩子框架,有大量插件。
8. 实用效率技巧
命令别名
把长命令简化成短命令,提升输入效率,推荐配置:
git config --global alias.st status
git config --global alias.co checkout
git config --global alias.br branch
git config --global alias.cm commit
git config --global alias.sw switch
git config --global alias.lg "log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit"
git config --global alias.last "log -1 HEAD"
git config --global alias.unstage "reset HEAD --"快速切换
# 切换到上一个分支
git switch -清理仓库
# 清理未跟踪的文件和目录(-f 文件,-d 目录)
# 注意:会永久删除未跟踪的文件,确认清楚再用
git clean -fd
# 手动触发垃圾回收,压缩仓库体积
git gc查看提交统计
# 统计每个人的提交数
git shortlog -sn
# 统计代码行数增减
git log --author="用户名" --pretty=tformat: --numstat | awk '{add+=$1; del+=$2} END {print "新增:"add" 行,删除:"del" 行"}'第七篇:团队协作工作流
不同规模的团队、不同类型的项目,适合的 Git 工作流不一样。选对工作流能大大提升协作效率,减少混乱。
1. GitHub Flow:轻量化工作流
最流行、最简单的工作流,适合中小团队、互联网产品、持续部署的项目。
核心思想
- 只有一个长期主分支
main,永远保持可发布、可部署状态; - 所有开发都在功能分支上进行,从 main 拉取,开发完提 PR 合并回 main;
- 合并后立即部署。
完整流程
- 创建分支:从 main 拉取新的功能分支
feature/xxx; - 开发提交:在功能分支上开发,小步频繁提交;
- 提交 PR:开发完成后,提交 Pull Request(Merge Request);
- Code Review:团队成员审查代码,提出修改意见,作者迭代修改;
- 自动化检查:CI 自动跑测试、构建、代码检查,通过后才能合并;
- 合并分支:审查通过后,合并到 main 分支;
- 部署上线:合并后自动部署到生产环境;
- 删除分支:功能分支合并完就删除。
PR 合并方式选择
- Merge Commit(普通合并):生成合并提交,保留完整分支历史。适合大功能,想保留完整开发记录;
- Squash Merge(压缩合并):把分支所有提交压缩成一个提交合入 main。主分支历史干净,一个功能一个提交,推荐小功能用;
- Rebase Merge(变基合并):把分支提交变基到 main 后面,历史一条直线。历史最清爽,但会重写提交哈希。
优缺点
- ✅ 简单易学,规则少,上手快;
- ✅ 迭代速度快,适合快速发布;
- ✅ PR 机制保证代码质量;
- ❌ 没有专门的发布、测试阶段,对自动化要求高;
- ❌ 不适合多版本并行维护、长周期版本的项目。
2. Git Flow:经典规范工作流
最经典的分支管理模型,适合中大型团队、传统软件、有明确版本发布周期的项目。
五大核心分支
| 分支 | 类型 | 说明 |
|---|---|---|
main / master | 长期分支 | 生产环境分支,存放正式发布的稳定代码,只能从 release 或 hotfix 合并进来 |
develop / dev | 长期分支 | 开发集成分支,所有功能分支都合到这里,存放最新的开发版本 |
feature/* | 短期分支 | 功能开发分支,从 develop 拉取,开发完合并回 develop |
release/* | 短期分支 | 发布准备分支,从 develop 拉取,用于版本测试、修bug、准备发布,完成后合并到 main 和 develop,打标签 |
hotfix/* | 短期分支 | 线上紧急修复分支,从 main 拉取,修复完合并回 main 和 develop,打补丁标签 |
完整工作流程
- 功能开发:从 develop 拉 feature 分支 → 开发 → 合并回 develop;
- 版本发布:版本功能开发完,从 develop 拉 release/v1.2.0 分支 → 测试、修复bug → 测试通过 → 合并到 main,打 tag v1.2.0 → 同时合并回 develop → 删除 release 分支;
- 线上热修:线上出 bug,从 main 拉 hotfix/v1.2.1 分支 → 修复bug → 测试通过 → 合并到 main,打 tag v1.2.1 → 同时合并回 develop → 删除 hotfix 分支。
优缺点
- ✅ 分支职责清晰,规范严格,适合大型团队;
- ✅ 版本管理清晰,多版本并行维护方便;
- ✅ 生产环境代码稳定,有专门的测试阶段;
- ❌ 分支多,流程复杂,学习成本高;
- ❌ 合并路径长,容易出现冲突;
- ❌ 发布周期长,不适合快速迭代的互联网产品。
3. Trunk Based Development:主干开发
大厂常用的高效工作流,配合特性开关和 CI/CD,迭代速度极快。
核心思想
- 只有一个主干分支
main,所有人都向主干提交代码; - 功能分支寿命极短(几小时到一两天),开发完立刻合并回主干;
- 未完成的功能用**特性开关(Feature Flag)**隐藏,代码可以先合入主干,不影响用户使用;
- 完善的自动化测试和 CI/CD 保证主干质量,每次合入都自动跑测试。
关键实践
- 小步提交:每次提交改动很小,每天至少合入一次主干,避免长期分叉;
- 特性开关:未完成、未上线的功能用开关控制,默认关闭,开发完再打开;
- 持续集成:每次提交都触发自动化测试、构建,确保主干永远可用;
- 代码审查:短生命周期的功能分支也通过 PR 合入,保证代码质量;
- 快速回滚:出问题可以快速回滚,或者关掉特性开关。
优缺点
- ✅ 迭代速度极快,开发效率最高;
- ✅ 几乎没有合并冲突,分支寿命短,同步频繁;
- ✅ 没有长期分支维护成本;
- ❌ 对团队技术能力、自动化测试水平要求极高;
- ❌ 特性开关管理不好会带来技术债务;
- ❌ 不适合新手团队。
4. Forking Workflow:开源项目工作流
开源社区的标准工作流,适合大量外部贡献者、权限管控严格的项目。
核心思想
- 主仓库只有维护者有写权限,贡献者不能直接推送;
- 贡献者先 Fork 主仓库到自己账号下,得到一个完整的副本;
- 贡献者在自己的副本里建分支开发,完成后向上游主仓库提交 PR;
- 维护者审查 PR,没问题就合并入主仓库。
同步上游更新
自己的 Fork 仓库要定期同步主仓库的最新代码:
# 添加上游远程仓库
git remote add upstream 主仓库地址
# 拉取上游更新
git fetch upstream
# 合并到本地 main
git switch main
git merge upstream/main
# 推送到自己的远程仓库
git push origin main优缺点
- ✅ 权限管控好,外部贡献者不需要写权限;
- ✅ 适合大量贡献者的开源项目;
- ❌ 流程相对繁琐,贡献者需要维护自己的副本。
5. 工作流选型建议
| 团队规模 | 项目类型 | 推荐工作流 |
|---|---|---|
| 1-5人小团队 | 互联网产品、快速迭代 | GitHub Flow |
| 中大型团队 | 传统软件、企业系统、版本发布周期长 | Git Flow |
| 成熟大厂团队 | 高频发布、自动化完善 | Trunk Based Development |
| 开源项目 | 社区贡献 | Forking + GitHub Flow |
没有最好的工作流,只有最适合的。从小团队简单的 GitHub Flow 开始,团队壮大了再逐步完善规范。
6. 团队协作最佳实践
- 统一配置:团队统一换行符配置、提交规范、忽略规则,避免不必要的冲突和格式问题;
- 小步提交:一个提交只做一件事,不要攒大提交,方便回滚、排查、审查;
- 频繁同步:每天至少拉一次主分支更新到自己的分支,减少冲突积累;
- 提交前自检:提交前自己看一遍改了什么,跑一下测试,不要把调试代码、bug 提交上去;
- Code Review:所有合并到主分支的代码都要经过审查,提升代码质量,也能团队共享知识;
- 保护主分支:设置分支保护,禁止直接推送,必须走 PR 合并,必须审查通过、CI 通过才能合并;
- 分支用完即删:功能分支合并后就删除,保持分支列表干净,避免一堆废弃分支;
- 写好 README 和贡献指南:新人来了知道怎么上手,规范清晰。
第八篇:常见问题与避坑指南
1. 操作类常见问题
中文文件名乱码
- 问题:Git 里中文文件名显示成数字编码;
- 解决:
git config --global core.quotepath false
换行符混乱,跨平台全是修改
- 问题:Windows 和 Mac/Linux 换行符不一样,每次拉取都显示一堆修改;
- 解决:
- Windows:
git config --global core.autocrlf true - Mac/Linux:
git config --global core.autocrlf input - 仓库级统一:项目根目录加
.gitattributes文件,指定换行符规则。
- Windows:
分离头指针状态 Detached HEAD
- 原因:直接 checkout 了某个提交、标签,HEAD 没有指向任何分支;
- 风险:在这个状态下提交的代码,切换分支后会丢失,因为没有分支指向这些提交;
- 解决:如果想保留修改,新建一个分支:
git switch -c 新分支名 - 如果只是看看,切回正常分支就行:
git switch main
git pull 报错 refusing to merge unrelated histories
- 原因:两个仓库没有共同提交历史,比如本地 init 的仓库和远程空仓库关联;
- 解决:
git pull origin main --allow-unrelated-histories
推送被拒绝 failed to push some refs
- 常见原因:远程有新提交,本地落后了;
- 解决:先
git pull拉取最新代码,解决冲突,再推送; - 如果是你自己的分支,历史被你重写了,用
git push --force-with-lease。
远程分支删了,本地还能看到
- 解决:
git fetch --prune,清理远程已经删除的本地跟踪分支。
想忽略已经跟踪的文件的修改
- 场景:配置文件改了本地参数,不想提交,也不想影响远程;
- 命令:
git update-index --assume-unchanged 文件名 - 取消:
git update-index --no-assume-unchanged 文件名
2. 敏感信息泄露处理
不小心把密码、密钥、私密配置提交进 Git 了,怎么办?
- 立刻止损:马上修改泄露的密码、密钥,这是最重要的,历史里的删不掉,先让泄露的东西失效;
- 清理历史:
- 推荐工具:
git filter-repo(官方推荐)、BFG Repo-Cleaner(简单快速) - 可以删除指定文件、替换指定字符串;
- 推荐工具:
- 强制推送:清理完强制推送覆盖远程(注意通知团队所有人重新克隆);
- 预防:
- 敏感配置用环境变量,不要写进代码;
- 用
.gitignore忽略配置文件,提供.example模板; - 配置 pre-commit 钩子,提交前扫描敏感信息。
3. 仓库体积过大优化
Git 仓库越来越大,克隆拉取很慢怎么办?
- 浅克隆:只拉最新版本,不要历史:
git clone --depth=1 仓库地址,适合只需要用代码的场景; - 单分支克隆:只克隆指定分支:
git clone --single-branch --branch main 仓库地址; - 清理历史大文件:用
git filter-repo或 BFG 删除历史大文件; - 大文件用 LFS:二进制大文件、图片、模型等用 Git LFS 管理;
- 垃圾回收:
git gc --prune=now --aggressive,压缩仓库对象,减小体积。
4. 经典踩坑合集
- ❌ 公共分支强制推送:毁灭性操作,会搞乱所有人的历史,绝对禁止;
- ❌ 提交大文件、依赖包、构建产物:仓库会越来越臃肿,拉取越来越慢;
- ❌ 提交敏感信息:密码、密钥、私密配置,泄露风险极大;
- ❌ 无意义提交信息:“更新”、“修复”、“asdf”,以后根本不知道改了啥;
- ❌ 攒大提交:一个提交改十几个功能,出问题没法单独回滚;
- ❌ 长期不同步主分支:分支越久冲突越多,合并的时候地狱级冲突;
- ❌ 分离头指针状态下写代码:切个分支代码就没了;
- ❌ 不懂原理瞎用命令:比如随便
reset --hard、force push,出问题就慌。
5. 出问题怎么办
- 不要慌,Git 几乎所有操作都是可恢复的,只要提交过大概率能找回来;
- 先
git status、git log、git reflog看看当前状态; - 危险操作前先打个 tag 或者建个备份分支,留后路;
- 不确定的命令先查清楚再执行,不要瞎试;
- 真出问题了找 reflog,99% 能救回来。
第九篇:底层原理深入理解
1. Git 是什么
Git 本质上是一个内容寻址的文件系统,版本控制是它的上层表现。它把所有内容都以对象的形式存在 .git/objects 目录里,通过 SHA-1 哈希值来索引。
2. 四大 Git 对象
Blob 对象
- 存储文件的内容,只存内容,不存文件名、路径、权限;
- 相同内容的文件,不管文件名是什么,在 Git 里只存一个 blob,节省空间;
- 内容稍微改一点,SHA-1 就完全不一样,生成新的 blob。
Tree 对象
- 对应文件系统的目录,记录目录结构;
- 里面包含多个条目,每个条目有:权限、类型(blob 或子 tree)、哈希值、文件名;
- 一个顶级 tree 对象就对应整个项目在某个提交时的快照。
Commit 对象
- 一次提交对应一个 commit 对象;
- 包含内容:指向顶级 tree 的哈希、父提交哈希(第一个提交没有父提交,合并提交有两个父提交)、作者、提交者、时间戳、提交信息;
- 所有 commit 通过父提交指针串联起来,形成提交历史链。
Tag 对象
- 附注标签是独立的对象,包含标签名、创建者、时间、说明、指向的 commit 哈希;
- 可以 GPG 签名,验证标签的真实性;
- 轻量标签不是对象,只是一个指向 commit 的引用。
3. Git 引用(References)
分支、标签、HEAD 本质上都是引用,就是指向 commit 哈希的指针,存在 .git/refs 目录下。
- 分支:
refs/heads/下,指向分支最新的提交,提交的时候自动向前移动; - 标签:
refs/tags/下,指向某个提交,固定不动; - 远程跟踪分支:
refs/remotes/下,记录远程分支的状态,fetch 的时候更新; - HEAD:特殊引用,指向当前所在的分支(或者直接指向 commit,就是分离头指针)。
4. 暂存区(Index)
- 就是
.git/index文件,记录下一次提交要生成的 tree 内容; git add就是把文件内容生成 blob,然后更新暂存区的条目;git commit就是根据暂存区生成 tree 对象,然后创建 commit 对象,更新分支指针。
5. 工作区、暂存区、本地仓库的流转
- 你修改工作区的文件,Git 检测到文件变化;
git add把文件内容生成 blob 放进对象库,更新暂存区;git commit根据暂存区生成 tree,创建 commit 对象,更新当前分支指针指向新的 commit。
6. 合并原理:三路合并
Git 合并不是简单的文件覆盖,而是三路合并算法:
- 找到两个分支的共同祖先提交;
- 以共同祖先为基准,对比两个分支分别改了什么;
- 两个分支改了不同的地方,自动合并;
- 改了同一个地方,Git 不知道听谁的,就报冲突,人工解决。
7. 为什么 Git 分支又快又轻
因为创建分支只是写一个 40 字节的哈希值到文件里,几乎零成本,和项目大小、文件多少完全没关系。切换分支也只是改 HEAD 指针,然后把工作区更新成分支对应的快照,速度极快。
对比 SVN 的分支是完整复制一遍目录,文件越多越慢,这是本质上的差距。
学习资源与进阶路径
推荐学习资源
- Pro Git(官方电子书):最权威最全面的 Git 书籍,有免费中文版,入门到进阶都适合;
- Git 官方文档:最权威的参考资料,遇到问题查官方文档最准;
- Learn Git Branching:交互式学习网站,通过游戏学分支和变基,非常直观,适合理解原理;
- 廖雪峰 Git 教程:经典入门教程,通俗易懂,适合新手;
- Git 飞行指南(Git Flight Rules):各种常见问题的解决方案字典,遇到问题可以查。
学习路径建议
- 入门阶段:安装配置、基础命令(add/commit/status/log/diff)、远程仓库推拉;
- 进阶阶段:分支管理、合并冲突、撤销回滚、标签、常用高级命令;
- 高级阶段:rebase、cherry-pick、bisect、子模块、LFS、Hooks、工作流选型;
- 原理阶段:Git 对象模型、底层原理、内部命令,知其然知其所以然。
练习方法
- 最好的学习就是多用,在实际项目里用,遇到问题解决问题,进步最快;
- 可以建个测试仓库,随便折腾各种命令,不用担心搞坏,大不了删了重建;
- 新手可以先用 GUI 工具上手,理解概念,再逐步用命令行,命令行是 Git 的完全体,功能最强大。
