Home
avatar

Qiu

Git版本管理说明

AI 总结

面向零基础到进阶的 Git 实战手册,用「写作文/存档」的通俗类比讲解核心概念。覆盖从环境搭建、日常提交、分支管理、远程协作,到撤销回滚、高级技巧(rebase、cherry-pick 等)及团队工作流的完整知识链,每个命令均附具体场景、示例与避坑提醒,适合作为随时翻阅的操作速查指南。

前置必看:Git 核心概念

1. 什么是版本控制

版本控制系统(VCS)是用来记录文件内容变化、方便后续查阅特定版本、回溯历史、多人协作的工具。它主要解决三类痛点:

  • 历史回溯:写错了代码想回到上个版本,不用手动备份一堆“最终版_v1_v2_最终版”文件;
  • 多人协作:多个人同时改同一个项目,不会互相覆盖修改,能自动合并不同人的改动;
  • 责任追溯:某行代码是谁改的、什么时候改的、为什么改,都能查到完整记录。

版本控制系统经历了三代演进:

  1. 本地版本控制:用本地数据库记录文件差异,比如 RCS,只能自己用,没法协作;
  2. 集中式版本控制(CVCS):比如 SVN、CVS,所有版本数据都存在中央服务器,所有人都从服务器拉取代码、提交修改。缺点是单点故障,服务器挂了所有人都没法工作,离线不能提交;
  3. 分布式版本控制(DVCS):比如 Git,每个人的本地都有完整的版本仓库,不依赖中央服务器,离线也能正常提交,服务器只是用来方便同步大家的改动,挂了也不影响本地工作。

2. Git 四大工作区域

Git 的工作流程围绕四个核心区域流转,理解了它们就理解了 Git 一半的逻辑:

区域通俗类比作用说明
工作区(Working Directory)你正在写的草稿纸电脑里能直接看到、编辑的项目文件,我们日常写代码都在这个区域
暂存区(Staging Area / Index)待定稿文件夹临时存放下次要提交的改动,相当于提交前的确认区,可以分批挑选要提交的内容
本地仓库(Local Repository)你的存档柜所有提交过的历史版本都永久保存在这里,数据存在本地 .git 目录下
远程仓库(Remote Repository)云端备份网盘托管在网络服务器上的仓库,比如 GitHub、Gitee、GitLab,用来多人共享、异地备份

3. 文件的四种状态

在 Git 眼中,项目里的文件永远处于以下四种状态之一,通过 git status 可以随时查看:

  1. 未跟踪(Untracked):新创建的文件,Git 还没认识它,不在版本控制范围内;
  2. 已修改(Modified):文件被改了,但还没放进暂存区;
  3. 已暂存(Staged):文件已经放进暂存区,下次提交就会被记录进版本历史;
  4. 已提交(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 平台

  1. Git 官网 下载对应系统位数的安装包;
  2. 运行安装程序,关键选项说明:
    • 组件选择:默认勾选 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 好用;
  3. 安装完成后,打开 Git Bash,输入 git --version,出现版本号就是安装成功。

macOS 平台

有三种常用安装方式:

  1. Xcode 命令行工具:终端输入 git --version,如果没安装会自动弹出提示,跟着引导安装即可,最省心;
  2. Homebrew 安装:先装 Homebrew,然后终端执行 brew install git,适合习惯包管理的用户;
  3. 官网安装包:去 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 的配置有三个层级,优先级从高到低:

  1. 仓库级(local):只对当前仓库生效,配置存在项目 .git/config 里;
  2. 用户级(global):对当前操作系统用户的所有仓库生效,配置存在用户目录 ~/.gitconfig 里;
  3. 系统级(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-origin

3. 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 为例:

  1. 复制公钥内容:打开 ~/.ssh/id_ed25ssh/id_ed25519.pub 文件,全选复制所有内容;
    • Windows:可以用 cat ~/.ssh/id_ed25519.pub | clip 一键复制到剪贴板;
    • Mac:pbcopy < ~/.ssh/id_ed25519.pub
  2. 登录 GitHub,进入 Settings → SSH and GPG keys → New SSH key;
  3. 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_company

4. 两种项目起步方式

方式一:本地全新项目初始化

如果你本地已经有项目文件,或者从零开始新建项目,用 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: 修复 bug
  • docs: 文档修改
  • style: 代码格式调整,不影响逻辑(比如空格、格式化、分号)
  • refactor: 代码重构,没有加新功能也没修 bug
  • perf: 性能优化
  • 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_StoreThumbs.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 --abort

3. 合并冲突完全指南

冲突什么时候会出现

两个分支修改了同一个文件的同一行代码,或者一个删了文件一个改了文件,Git 不知道该保留哪个版本,就会报冲突,需要人工手动解决。

二进制文件(比如图片、压缩包)冲突更麻烦,没法逐行合并,只能选保留其中一个版本。

冲突标记解读

出现冲突后,有冲突的文件会被加上特殊标记:

<<<<<<< HEAD
首页标题:我的个人博客
=======
首页标题:技术分享站
>>>>>>> feature-login
  • <<<<<<< HEAD=======:当前分支的内容;
  • =======>>>>>>> 分支名:要合并的分支的内容;
  • 你需要决定最终保留什么,删掉所有标记行,保存文件。

解决冲突的标准步骤

  1. 执行 git status 找到标红的冲突文件;
  2. 打开冲突文件,找到 <<<<<<< 标记的位置;
  3. 编辑文件,决定最终保留的内容,删除所有冲突标记;
  4. 保存文件,执行 git add 冲突文件名,把解决后的文件放进暂存区;
  5. 执行 git commit 完成合并,提交信息会自动生成为合并提交。

冲突解决工具

手动改文件比较麻烦,可以用可视化工具:

  • VS Code:内置冲突编辑器,会标红冲突区域,有四个按钮“接受当前更改”、“接受传入更改”、“接受双方更改”、“比较差异”,点按钮就能解决,非常方便;
  • Vimdiff:命令行自带的对比工具;
  • Beyond Compare:专业的对比工具,功能强大。

配置默认合并工具:

# 配置 VS Code 为默认 diff 和 merge 工具
git config --global diff.tool vscode
git config --global merge.tool vscode

减少冲突的最佳实践

  1. 频繁同步主分支:每天把主分支的最新代码拉到自己的功能分支,不要等开发完才合并,攒得越久冲突越多;
  2. 小步提交:不要攒大提交,拆成小提交,合并冲突也好解决;
  3. 按模块分工:团队里尽量不同人改不同的文件,减少多人改同一处的概率;
  4. 提前沟通:要改公共的核心文件,提前和团队说一声,避免两个人同时大改。

4. 通用分支命名规范

统一的命名规范能让分支一目了然,不用点开就知道这个分支是干嘛的。推荐格式:类型/描述

分支类型命名格式例子说明
主分支main / mastermain生产环境稳定版本,受保护,不能直接提交
开发集成分支develop / devdev日常开发集成,所有功能分支合到这里
功能分支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 main

2. 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)之后,才需要强制推送。

推送被拒绝的常见原因

  1. 远程有新提交,本地不是最新:最常见,提示 non-fast-forward,先 git pull 拉取最新代码,解决冲突,再推送;
  2. 没有权限:账号没有这个仓库的推送权限,找管理员开权限;
  3. 分支受保护:主分支设置了保护,不能直接推送,需要提 PR 合并;
  4. 本地历史和远程不一致:比如你 reset 了本地分支,和远程分叉了,需要强制推送(仅限个人分支)。

3. git fetch:获取远程更新

把远程仓库的所有最新提交、分支、标签,下载到本地,更新远程跟踪分支(origin/main 这种),但完全不会修改你的工作区和当前分支,非常安全。

# 获取 origin 远程所有更新
git fetch origin

# 只获取指定分支的更新
git fetch origin main

# 获取所有远程仓库的更新
git fetch --all

# 清理远程已经删除的本地跟踪分支
git fetch --prune

fetch 之后你可以做什么:

  • 查看远程更新了什么: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-histories

5. 标签 Tag 管理

标签就是给某个提交打个标记,比如版本号 v1.0.0,方便以后快速定位到这个版本,一般用于发布正式版本。

和分支的区别:分支指针会随着提交往前移动,标签打了之后就固定在那个提交上,永远不会动。

两种标签类型

1. 轻量标签(Lightweight)

本质就是一个指向提交的指针,没有额外信息,相当于给提交哈希起个别名。

git tag v1.0.0
2. 附注标签(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。


明白了,需要做两个调整:

  1. 章节编号:中文数字“二、三、四、五、六、七、八”改成阿拉伯数字“2. 3. 4. 5. 6. 7. 8.”,作为第五篇的下一级标题。
  2. 正文层级1.11.2 等子标题下面的“核心原理”“适用场景”“效果总结”等小标题,改为正文加粗,不再作为子标题。

以下是修改后的完整文档:


第五篇:撤销与回滚大全(按场景查表)

很多新手学Git最头疼的就是「撤销操作」:改乱了代码想撤回、提交错了想回退、不小心删了分支想找回……不同的阶段对应完全不同的命令,用错了轻则代码混乱,重则永久丢失代码。

所有撤销操作的本质,都是对「工作区、暂存区、本地仓库」三大区域做不同程度的回退。理解了三大区域的流转逻辑,就能彻底搞懂所有撤销命令,不用死记硬背。


0. 前置说明:危险等级与核心原则

危险等级划分

等级标识说明
安全级🟢完全不会丢失代码,放心使用
注意级🟡操作不当可能丢失代码,确认后再执行
危险级🔴会永久删除代码/修改公共历史,非必要不用

核心操作原则

  1. 公共分支绝不重写历史:已经推送到团队共享的main/dev分支,永远不要用reset/rebase等修改历史的命令后强制推送,优先用git revert
  2. 危险操作留后路:拿不准的回退操作,先打个标签git tag backup或者新建备份分支,出问题可以一键恢复。
  3. 先看状态再操作:执行任何撤销命令前,先敲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的内容还完整保留在暂存区。

危险等级:🟢 完全安全,不会丢失任何代码。

适用场景

  1. 刚提交完发现提交信息写错了,想撤回重写;
  2. 提交完发现漏加了一个文件,想撤回来补上再一起提交;
  3. 想把连续的几个小提交合并成一个大提交。

完整操作演示

  1. 执行回退命令
git reset --soft HEAD~1
  1. 验证1:提交历史变化
git log --oneline

输出:

xxxxxxx (HEAD -> main) C2:第二次提交
xxxxxxx C1:第一次提交

✅ C3提交从历史里消失了,main指针移动到了C2。

  1. 验证2:暂存区状态
git status

输出:

On branch main
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	modified:   demo.txt

✅ C3提交的所有修改,还完整留在暂存区里,处于「已暂存,未提交」的状态。

  1. 验证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 commitgit add操作,修改还留在工作区,你可以重新编辑后再提交。

注意:这是git reset默认模式,不加任何参数的时候,自动就是--mixed模式。

危险等级:🟢 安全,不会丢失代码,所有修改都保留在工作区。

适用场景

  1. 提交的内容有错误,想撤回来修改完再重新提交;
  2. 一次性add了太多文件,想拆分成多个提交分批提交;
  3. 不小心add了不该提交的文件,想从暂存区撤出来。

完整操作演示

我们先把仓库恢复到初始的C3状态,再做演示:

# 重新提交C3,回到初始状态
echo "第3行:最新内容" >> demo.txt
git add .
git commit -m "C3:第三次提交"
  1. 执行回退命令
# 不加参数,默认就是--mixed模式
git reset HEAD~1
# 等价于 git reset --mixed HEAD~1
  1. 验证1:提交历史变化
git log --oneline

输出:

xxxxxxx (HEAD -> main) C2:第二次提交
xxxxxxx C1:第一次提交

✅ 和soft模式一样,C3提交从历史里消失,main指针移动到C2。

  1. 验证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的修改从暂存区撤回到了工作区,处于「已修改,未暂存」的状态。

  1. 验证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 硬重置(最危险,全量覆盖)

核心原理:移动分支指针到目标提交,清空暂存区,同时直接覆盖工作区的所有文件,三个区域完全同步到目标提交的状态。目标提交之后的所有修改,无论是暂存区还是工作区的,都会被彻底删除,无法找回(除非你已经提交过)。

危险等级:🔴 高危操作!会永久删除未提交的代码,执行前务必确认。

适用场景

  1. 某个提交的代码完全写错了,彻底不想要了,想干净利落地回到某个版本;
  2. 本地代码改得一团糟,想完全恢复成远程仓库的干净状态;
  3. 个人分支重写历史后,强制同步本地状态。

完整操作演示

再次把仓库恢复到初始的C3状态:

echo "第3行:最新内容" >> demo.txt
git add .
git commit -m "C3:第三次提交"
  1. 执行回退命令
git reset --hard HEAD~1
  1. 验证1:提交历史变化
git log --oneline

输出:

xxxxxxx (HEAD -> main) C2:第二次提交
xxxxxxx C1:第一次提交

✅ 和前两种模式一样,C3提交从历史里消失,main指针移动到C2。

  1. 验证2:暂存区状态
git status

输出:

On branch main
nothing to commit, working tree clean

✅ 暂存区完全清空,和C2提交状态一致。

  1. 验证3:工作区内容
cat demo.txt

输出只有两行:

第1行:初始内容
第2行:新增内容

✅ 工作区被完全覆盖了!C3新增的「第3行」彻底消失了,整个文件回到了C2提交时的状态。

效果总结git reset --hard 是最彻底的回退,三个区域完全同步到目标提交的状态,所有之后的修改都会被删除。相当于彻底回到了过去的某个版本,就像后面的提交从来没发生过一样。

特别警告

  1. 执行--hard前,一定要确认工作区和暂存区里没有需要保留的代码,一旦执行就找不回来了(除非你已经提交过,可以用reflog找回)。
  2. 绝对不要在有未提交重要代码的仓库里随便执行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~3
1.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 红线与避坑指南

  1. 公共分支绝对禁用reset+强推 已经推送到团队共享的main/dev分支,不要用git reset回退然后git push --force强制覆盖。这会导致所有团队成员的本地历史和远程不一致,产生大量冲突,甚至丢失别人的提交。 公共分支想撤销提交,永远优先用git revert(后面会讲)。

  2. —hard 执行前必确认git reset --hard之前,先停一秒,确认:

    • 工作区有没有未提交的重要代码?
    • 是不是真的完全不需要目标提交之后的所有内容? 拿不准就先打个备份标签:git tag backup_before_reset,出问题直接恢复。
  3. 不要在分离头指针状态下随便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会先检查:如果远程分支有你不知道的新提交,就会拒绝推送,不会覆盖别人的代码,是更安全的强制推送方式。

绝对禁令:❌ 绝对不要对maindev等公共分支执行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 pop

5.4 常见问题

  1. 取出储藏时有冲突怎么办? 和合并冲突处理方式完全一样:手动解决冲突 → git add冲突文件 → 完成。冲突解决后,stash记录不会自动删除,需要手动git stash drop删掉。

  2. 想把储藏应用到别的分支? 先切换到目标分支,再执行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提交删掉了,现在想恢复回来。

恢复步骤

  1. 查看操作历史,找到目标提交
git reflog

找到reset之前的那次操作,比如HEAD@{1}对应的就是reset前的C3提交,记下它的哈希。

  1. 执行恢复 方案A(直接重置回去):
git reset --hard 目标提交哈希

方案B(更安全,新建分支恢复):

git switch -c recovery-branch 目标提交哈希

恢复完可以验证一下,代码完整回来了。

6.4 实战场景2:误删分支,恢复整个分支

症状:不小心git branch -D删掉了还没合并的功能分支,里面有写了一半的代码。

恢复步骤

  1. 用reflog找到被删分支的最后一次提交哈希 删分支的操作也会被reflog记录下来,找到那个分支最后一次提交的哈希。

  2. 重建分支

git switch -c 恢复后的分支名 目标提交哈希

整个分支就完整回来了,所有提交历史都在。

6.5 注意事项

  1. reflog是本地仓库独有的,只记录你本地的操作,不会同步到远程仓库。远程仓库的操作不会出现在你的本地reflog里。
  2. 默认保留30天,超过时间的记录可能会被Git垃圾回收清理掉。所以误操作了要尽快找,不要等太久。
  3. 只要代码曾经提交过,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. 新手避坑总结

  1. 先分阶段,再选命令:先想清楚你的修改在哪个区域(工作区/暂存区/已提交/已推送),再选对应的撤销命令,不要上来就瞎试。
  2. 公共分支永远用revert:只要是团队共享的分支,撤销提交永远优先用git revert,不要重写历史。
  3. 危险操作留后路:拿不准的reset、rebase操作,先打个tag或者建个备份分支,出问题一键恢复。
  4. 丢了代码先找reflog:不要慌,Git比你想象的更安全,只要提交过,基本都能找回来。
  5. 多练多用:建个测试仓库随便折腾各种命令,踩过几次坑就彻底记住了,比死记硬背管用得多。

第六篇:高级进阶技巧

1. git rebase:变基

什么是变基

通俗说就是“换基准”,把你当前分支的所有提交,整体搬运到目标分支的最新提交后面。

  • merge 是三方合并,生成新的合并提交,历史有分叉;
  • rebase 是把提交一个个重新应用一遍,生成新的提交哈希,历史是一条直线。

基础用法:同步主分支代码

场景:你在功能分支开发,主分支更新了,想把主分支的最新代码同步过来。

# 1. 先切到你的功能分支
git switch feature-login

# 2. 拉取远程最新
git fetch origin

# 3. 变基到主分支
git rebase origin/main

同步完你的分支提交都在主分支最新提交后面,历史是一条直线,比 merge 清爽很多。

变基过程中的冲突处理

变基过程中遇到冲突,rebase 会暂停,等你解决冲突:

  1. 手动解决冲突文件;
  2. git add 冲突文件,把解决后的文件放进暂存区;
  3. 执行 git rebase --continue,继续变基过程;
  4. 如果中途想放弃,执行 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 一样:

  1. 解决冲突文件;
  2. git add 暂存;
  3. git cherry-pick --continue 继续;
  4. 放弃: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.sh

Git 会自动二分,自动跑脚本,直到找到第一个测试失败的提交。

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 服务器上,拉取的时候只下载当前版本需要的大文件。

安装使用

  1. 安装 Git LFS 客户端;
  2. 仓库初始化:git lfs install
  3. 跟踪大文件类型:git lfs track "*.psd" git lfs track "*.zip"
  4. 正常 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 平台用。

常用客户端钩子

  1. pre-commit:提交前执行,最常用。比如运行 ESLint 检查代码规范、Prettier 格式化、跑单元测试,不通过就阻止提交,保证代码质量。
  2. commit-msg:提交信息生成后执行,校验提交信息是否符合规范(比如 Conventional Commits)。
  3. pre-push:推送前执行,跑完整测试、构建检查,不通过不让推送。
  4. 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;
  • 合并后立即部署。

完整流程

  1. 创建分支:从 main 拉取新的功能分支 feature/xxx
  2. 开发提交:在功能分支上开发,小步频繁提交;
  3. 提交 PR:开发完成后,提交 Pull Request(Merge Request);
  4. Code Review:团队成员审查代码,提出修改意见,作者迭代修改;
  5. 自动化检查:CI 自动跑测试、构建、代码检查,通过后才能合并;
  6. 合并分支:审查通过后,合并到 main 分支;
  7. 部署上线:合并后自动部署到生产环境;
  8. 删除分支:功能分支合并完就删除。

PR 合并方式选择

  1. Merge Commit(普通合并):生成合并提交,保留完整分支历史。适合大功能,想保留完整开发记录;
  2. Squash Merge(压缩合并):把分支所有提交压缩成一个提交合入 main。主分支历史干净,一个功能一个提交,推荐小功能用;
  3. 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,打补丁标签

完整工作流程

  1. 功能开发:从 develop 拉 feature 分支 → 开发 → 合并回 develop;
  2. 版本发布:版本功能开发完,从 develop 拉 release/v1.2.0 分支 → 测试、修复bug → 测试通过 → 合并到 main,打 tag v1.2.0 → 同时合并回 develop → 删除 release 分支;
  3. 线上热修:线上出 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 保证主干质量,每次合入都自动跑测试。

关键实践

  1. 小步提交:每次提交改动很小,每天至少合入一次主干,避免长期分叉;
  2. 特性开关:未完成、未上线的功能用开关控制,默认关闭,开发完再打开;
  3. 持续集成:每次提交都触发自动化测试、构建,确保主干永远可用;
  4. 代码审查:短生命周期的功能分支也通过 PR 合入,保证代码质量;
  5. 快速回滚:出问题可以快速回滚,或者关掉特性开关。

优缺点

  • ✅ 迭代速度极快,开发效率最高;
  • ✅ 几乎没有合并冲突,分支寿命短,同步频繁;
  • ✅ 没有长期分支维护成本;
  • ❌ 对团队技术能力、自动化测试水平要求极高;
  • ❌ 特性开关管理不好会带来技术债务;
  • ❌ 不适合新手团队。

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. 团队协作最佳实践

  1. 统一配置:团队统一换行符配置、提交规范、忽略规则,避免不必要的冲突和格式问题;
  2. 小步提交:一个提交只做一件事,不要攒大提交,方便回滚、排查、审查;
  3. 频繁同步:每天至少拉一次主分支更新到自己的分支,减少冲突积累;
  4. 提交前自检:提交前自己看一遍改了什么,跑一下测试,不要把调试代码、bug 提交上去;
  5. Code Review:所有合并到主分支的代码都要经过审查,提升代码质量,也能团队共享知识;
  6. 保护主分支:设置分支保护,禁止直接推送,必须走 PR 合并,必须审查通过、CI 通过才能合并;
  7. 分支用完即删:功能分支合并后就删除,保持分支列表干净,避免一堆废弃分支;
  8. 写好 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 文件,指定换行符规则。

分离头指针状态 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 了,怎么办?

  1. 立刻止损:马上修改泄露的密码、密钥,这是最重要的,历史里的删不掉,先让泄露的东西失效;
  2. 清理历史
    • 推荐工具:git filter-repo(官方推荐)、BFG Repo-Cleaner(简单快速)
    • 可以删除指定文件、替换指定字符串;
  3. 强制推送:清理完强制推送覆盖远程(注意通知团队所有人重新克隆);
  4. 预防
    • 敏感配置用环境变量,不要写进代码;
    • .gitignore 忽略配置文件,提供 .example 模板;
    • 配置 pre-commit 钩子,提交前扫描敏感信息。

3. 仓库体积过大优化

Git 仓库越来越大,克隆拉取很慢怎么办?

  1. 浅克隆:只拉最新版本,不要历史:git clone --depth=1 仓库地址,适合只需要用代码的场景;
  2. 单分支克隆:只克隆指定分支:git clone --single-branch --branch main 仓库地址
  3. 清理历史大文件:用 git filter-repo 或 BFG 删除历史大文件;
  4. 大文件用 LFS:二进制大文件、图片、模型等用 Git LFS 管理;
  5. 垃圾回收git gc --prune=now --aggressive,压缩仓库对象,减小体积。

4. 经典踩坑合集

  1. ❌ 公共分支强制推送:毁灭性操作,会搞乱所有人的历史,绝对禁止;
  2. ❌ 提交大文件、依赖包、构建产物:仓库会越来越臃肿,拉取越来越慢;
  3. ❌ 提交敏感信息:密码、密钥、私密配置,泄露风险极大;
  4. ❌ 无意义提交信息:“更新”、“修复”、“asdf”,以后根本不知道改了啥;
  5. ❌ 攒大提交:一个提交改十几个功能,出问题没法单独回滚;
  6. ❌ 长期不同步主分支:分支越久冲突越多,合并的时候地狱级冲突;
  7. ❌ 分离头指针状态下写代码:切个分支代码就没了;
  8. ❌ 不懂原理瞎用命令:比如随便 reset --hardforce push,出问题就慌。

5. 出问题怎么办

  1. 不要慌,Git 几乎所有操作都是可恢复的,只要提交过大概率能找回来;
  2. git statusgit loggit reflog 看看当前状态;
  3. 危险操作前先打个 tag 或者建个备份分支,留后路;
  4. 不确定的命令先查清楚再执行,不要瞎试;
  5. 真出问题了找 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. 工作区、暂存区、本地仓库的流转

  1. 你修改工作区的文件,Git 检测到文件变化;
  2. git add 把文件内容生成 blob 放进对象库,更新暂存区;
  3. git commit 根据暂存区生成 tree,创建 commit 对象,更新当前分支指针指向新的 commit。

6. 合并原理:三路合并

Git 合并不是简单的文件覆盖,而是三路合并算法:

  1. 找到两个分支的共同祖先提交
  2. 以共同祖先为基准,对比两个分支分别改了什么;
  3. 两个分支改了不同的地方,自动合并;
  4. 改了同一个地方,Git 不知道听谁的,就报冲突,人工解决。

7. 为什么 Git 分支又快又轻

因为创建分支只是写一个 40 字节的哈希值到文件里,几乎零成本,和项目大小、文件多少完全没关系。切换分支也只是改 HEAD 指针,然后把工作区更新成分支对应的快照,速度极快。

对比 SVN 的分支是完整复制一遍目录,文件越多越慢,这是本质上的差距。


学习资源与进阶路径

推荐学习资源

  1. Pro Git(官方电子书):最权威最全面的 Git 书籍,有免费中文版,入门到进阶都适合;
  2. Git 官方文档:最权威的参考资料,遇到问题查官方文档最准;
  3. Learn Git Branching:交互式学习网站,通过游戏学分支和变基,非常直观,适合理解原理;
  4. 廖雪峰 Git 教程:经典入门教程,通俗易懂,适合新手;
  5. Git 飞行指南(Git Flight Rules):各种常见问题的解决方案字典,遇到问题可以查。

学习路径建议

  1. 入门阶段:安装配置、基础命令(add/commit/status/log/diff)、远程仓库推拉;
  2. 进阶阶段:分支管理、合并冲突、撤销回滚、标签、常用高级命令;
  3. 高级阶段:rebase、cherry-pick、bisect、子模块、LFS、Hooks、工作流选型;
  4. 原理阶段:Git 对象模型、底层原理、内部命令,知其然知其所以然。

练习方法

  • 最好的学习就是多用,在实际项目里用,遇到问题解决问题,进步最快;
  • 可以建个测试仓库,随便折腾各种命令,不用担心搞坏,大不了删了重建;
  • 新手可以先用 GUI 工具上手,理解概念,再逐步用命令行,命令行是 Git 的完全体,功能最强大。
版本管理 git
Q-bot
hello!我是 Q-bot,我会唱、跳、rap,can i help you?