Git 从入门到实战:原理、工作流、分支管理、撤销恢复与团队协作

Git 是目前最流行的分布式版本控制系统。它不仅可以帮助我们保存代码历史,还能支持多人协作、分支开发、版本发布、问题定位和误操作恢复。

本文将从 Git 的底层概念讲起,逐步介绍日常命令、分支合并、远程协作、撤销恢复、团队工作流以及常见问题。即使你之前没有接触过 Git,也可以按照本文一步步学习和实践。


1. Git 是什么

Git 是一个开源的分布式版本控制系统,由 Linux 内核的创建者 Linus Torvalds 于 2005 年开发。

所谓版本控制,就是记录文件每一次修改,使开发者可以:

  • 查看某个文件由谁修改;
  • 比较不同版本之间的差异;
  • 恢复到之前的版本;
  • 同时开发多个功能;
  • 合并多人提交的代码;
  • 为发布版本创建标签;
  • 定位某个 Bug 是在哪次提交中引入的。

Git 不仅可以管理程序代码,也可以管理配置文件、脚本、文档以及其他文本内容。

1.1 Git 为什么叫"分布式版本控制"

在 Git 中,每个开发者本地通常都拥有一份完整仓库,包括:

  • 当前项目文件;
  • 完整提交历史;
  • 分支和标签;
  • Git 对象数据库;
  • 本地版本控制能力。

即使暂时没有网络,开发者仍然可以:

bash 复制代码
git status
git diff
git add
git commit
git log
git branch

只有在需要与其他人交换代码时,才需要执行:

bash 复制代码
git fetch
git pull
git push

1.2 Git 的主要特点

  • 分布式存储;
  • 分支创建速度快;
  • 支持离线提交;
  • 数据完整性较强;
  • 支持多种协作工作流;
  • 具有完整的历史恢复能力;
  • 适合从个人项目到大型开源项目的各种场景。

1.3 Git 记录的是快照

从概念上看,Git 每次提交保存的是项目在某个时间点的快照,而不是简单记录"第几行增加了什么"。

如果一个文件没有变化,新的提交会继续引用已有的文件对象,而不需要重复保存完全相同的内容。

需要注意的是,Git 在打包存储对象时可能使用差异压缩来节省空间,但它对用户呈现的核心模型仍然是"快照"。


2. Git、GitHub、GitLab 和 SVN 的区别

2.1 Git 和 GitHub 的区别

Git 是版本控制工具,GitHub 是基于 Git 的代码托管平台。

可以这样理解:

  • Git 类似于本地使用的版本管理软件;
  • GitHub 类似于存放 Git 仓库并支持团队协作的网站。

除了 GitHub,常见的 Git 托管平台还有:

  • GitLab;
  • Gitee;
  • Bitbucket;
  • Azure DevOps;
  • 企业自建 Git 服务。

即使不使用 GitHub,也可以正常使用 Git。

2.2 Git 和 GitLab 的区别

GitLab 同样是基于 Git 的代码托管与研发协作平台,通常还提供:

  • Merge Request;
  • Issue 管理;
  • CI/CD;
  • 权限控制;
  • 制品管理;
  • 代码审查;
  • 私有化部署。

GitHub 中通常称为 Pull Request,GitLab 中通常称为 Merge Request,它们的核心目的都是审查并合并代码。

2.3 Git 和 SVN 的区别

对比项 Git SVN
架构 分布式 集中式
本地是否有完整历史 通常有 通常没有
离线提交 支持 不支持或能力有限
分支成本 很低 相对较高
提交到哪里 先提交到本地仓库 通常直接提交到中央仓库
网络依赖 大部分操作不依赖网络 很多操作依赖服务器
工作流 灵活 相对集中
学习成本 相对较高 相对简单

Git 并不是在所有场景下都绝对优于 SVN,但在现代软件开发、开源协作和持续集成场景中,Git 已经成为主流选择。


3. Git 的核心工作区域

理解 Git,最重要的是理解以下几个区域:

  1. 工作区;
  2. 暂存区;
  3. 本地仓库;
  4. 远程仓库。

#mermaid-svg-hWTWcYASaFYXNUoM{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-hWTWcYASaFYXNUoM .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-hWTWcYASaFYXNUoM .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-hWTWcYASaFYXNUoM .error-icon{fill:#552222;}#mermaid-svg-hWTWcYASaFYXNUoM .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-hWTWcYASaFYXNUoM .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-hWTWcYASaFYXNUoM .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-hWTWcYASaFYXNUoM .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-hWTWcYASaFYXNUoM .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-hWTWcYASaFYXNUoM .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-hWTWcYASaFYXNUoM .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-hWTWcYASaFYXNUoM .marker{fill:#333333;stroke:#333333;}#mermaid-svg-hWTWcYASaFYXNUoM .marker.cross{stroke:#333333;}#mermaid-svg-hWTWcYASaFYXNUoM svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-hWTWcYASaFYXNUoM p{margin:0;}#mermaid-svg-hWTWcYASaFYXNUoM .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-hWTWcYASaFYXNUoM .cluster-label text{fill:#333;}#mermaid-svg-hWTWcYASaFYXNUoM .cluster-label span{color:#333;}#mermaid-svg-hWTWcYASaFYXNUoM .cluster-label span p{background-color:transparent;}#mermaid-svg-hWTWcYASaFYXNUoM .label text,#mermaid-svg-hWTWcYASaFYXNUoM span{fill:#333;color:#333;}#mermaid-svg-hWTWcYASaFYXNUoM .node rect,#mermaid-svg-hWTWcYASaFYXNUoM .node circle,#mermaid-svg-hWTWcYASaFYXNUoM .node ellipse,#mermaid-svg-hWTWcYASaFYXNUoM .node polygon,#mermaid-svg-hWTWcYASaFYXNUoM .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-hWTWcYASaFYXNUoM .rough-node .label text,#mermaid-svg-hWTWcYASaFYXNUoM .node .label text,#mermaid-svg-hWTWcYASaFYXNUoM .image-shape .label,#mermaid-svg-hWTWcYASaFYXNUoM .icon-shape .label{text-anchor:middle;}#mermaid-svg-hWTWcYASaFYXNUoM .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-hWTWcYASaFYXNUoM .rough-node .label,#mermaid-svg-hWTWcYASaFYXNUoM .node .label,#mermaid-svg-hWTWcYASaFYXNUoM .image-shape .label,#mermaid-svg-hWTWcYASaFYXNUoM .icon-shape .label{text-align:center;}#mermaid-svg-hWTWcYASaFYXNUoM .node.clickable{cursor:pointer;}#mermaid-svg-hWTWcYASaFYXNUoM .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-hWTWcYASaFYXNUoM .arrowheadPath{fill:#333333;}#mermaid-svg-hWTWcYASaFYXNUoM .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-hWTWcYASaFYXNUoM .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-hWTWcYASaFYXNUoM .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-hWTWcYASaFYXNUoM .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-hWTWcYASaFYXNUoM .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-hWTWcYASaFYXNUoM .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-hWTWcYASaFYXNUoM .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-hWTWcYASaFYXNUoM .cluster text{fill:#333;}#mermaid-svg-hWTWcYASaFYXNUoM .cluster span{color:#333;}#mermaid-svg-hWTWcYASaFYXNUoM div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-hWTWcYASaFYXNUoM .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-hWTWcYASaFYXNUoM rect.text{fill:none;stroke-width:0;}#mermaid-svg-hWTWcYASaFYXNUoM .icon-shape,#mermaid-svg-hWTWcYASaFYXNUoM .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-hWTWcYASaFYXNUoM .icon-shape p,#mermaid-svg-hWTWcYASaFYXNUoM .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-hWTWcYASaFYXNUoM .icon-shape .label rect,#mermaid-svg-hWTWcYASaFYXNUoM .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-hWTWcYASaFYXNUoM .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-hWTWcYASaFYXNUoM .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-hWTWcYASaFYXNUoM :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} git add
git commit
git push
git fetch
git restore / reset 等
工作区

Working Tree
暂存区

Index
本地仓库

Local Repository
远程仓库

Remote Repository

3.1 工作区

工作区就是当前可以直接看到和编辑的项目文件。

例如:

text 复制代码
my-project/
├── src/
├── pom.xml
├── README.md
└── .git/

除了 .git 目录之外,其余文件通常都属于工作区内容。

3.2 暂存区

暂存区也叫:

  • Staging Area;
  • Index;
  • 索引区。

执行下面的命令后,修改会进入暂存区:

bash 复制代码
git add README.md

暂存区的作用是让开发者决定"下一次提交具体包含哪些内容"。

假设你同时修改了三个文件,但只想提交其中两个,可以执行:

bash 复制代码
git add src/UserService.java
git add src/UserController.java
git commit -m "feat(user): add user query API"

未暂存的第三个文件不会进入这次提交。

3.3 本地仓库

执行 git commit 后,暂存区中的内容会形成一个提交并保存到本地仓库。

bash 复制代码
git commit -m "feat: add login function"

此时提交只存在于本地,并不会自动发送到 GitHub 或 GitLab。

3.4 远程仓库

远程仓库通常位于 GitHub、GitLab、Gitee 或企业内部服务器。

执行以下命令可以将本地提交推送到远程仓库:

bash 复制代码
git push origin main

其中:

  • origin 是远程仓库名称;
  • main 是需要推送的本地分支。

origin 只是 Git 在克隆仓库时通常创建的默认别名,并不是特殊关键字。一个仓库也可以配置多个远程地址。


4. Git 的对象模型与底层原理

一个 Git 仓库的核心数据位于 .git 目录中。

text 复制代码
.git/
├── HEAD
├── config
├── index
├── objects/
├── refs/
└── logs/

Git 的核心数据主要包括:

  • 对象;
  • 引用;
  • 索引;
  • Reflog。

4.1 Blob 对象

Blob 保存文件内容,但通常不保存文件名。

可以使用下面的命令把一个文件写入 Git 对象数据库:

bash 复制代码
git hash-object -w README.md

查看对象类型:

bash 复制代码
git cat-file -t <对象ID>

查看对象内容:

bash 复制代码
git cat-file -p <对象ID>

4.2 Tree 对象

Tree 对象表示目录结构,它可以引用:

  • Blob 对象;
  • 其他 Tree 对象。

Tree 对象负责把文件名、目录名和具体文件内容关联起来。

4.3 Commit 对象

Commit 对象通常包含:

  • 一个根 Tree;
  • 一个或多个父提交;
  • 作者信息;
  • 提交者信息;
  • 提交时间;
  • 提交说明。

#mermaid-svg-LCHuOgwKUS3Alyfa{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-LCHuOgwKUS3Alyfa .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-LCHuOgwKUS3Alyfa .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-LCHuOgwKUS3Alyfa .error-icon{fill:#552222;}#mermaid-svg-LCHuOgwKUS3Alyfa .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-LCHuOgwKUS3Alyfa .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-LCHuOgwKUS3Alyfa .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-LCHuOgwKUS3Alyfa .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-LCHuOgwKUS3Alyfa .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-LCHuOgwKUS3Alyfa .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-LCHuOgwKUS3Alyfa .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-LCHuOgwKUS3Alyfa .marker{fill:#333333;stroke:#333333;}#mermaid-svg-LCHuOgwKUS3Alyfa .marker.cross{stroke:#333333;}#mermaid-svg-LCHuOgwKUS3Alyfa svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-LCHuOgwKUS3Alyfa p{margin:0;}#mermaid-svg-LCHuOgwKUS3Alyfa .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-LCHuOgwKUS3Alyfa .cluster-label text{fill:#333;}#mermaid-svg-LCHuOgwKUS3Alyfa .cluster-label span{color:#333;}#mermaid-svg-LCHuOgwKUS3Alyfa .cluster-label span p{background-color:transparent;}#mermaid-svg-LCHuOgwKUS3Alyfa .label text,#mermaid-svg-LCHuOgwKUS3Alyfa span{fill:#333;color:#333;}#mermaid-svg-LCHuOgwKUS3Alyfa .node rect,#mermaid-svg-LCHuOgwKUS3Alyfa .node circle,#mermaid-svg-LCHuOgwKUS3Alyfa .node ellipse,#mermaid-svg-LCHuOgwKUS3Alyfa .node polygon,#mermaid-svg-LCHuOgwKUS3Alyfa .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-LCHuOgwKUS3Alyfa .rough-node .label text,#mermaid-svg-LCHuOgwKUS3Alyfa .node .label text,#mermaid-svg-LCHuOgwKUS3Alyfa .image-shape .label,#mermaid-svg-LCHuOgwKUS3Alyfa .icon-shape .label{text-anchor:middle;}#mermaid-svg-LCHuOgwKUS3Alyfa .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-LCHuOgwKUS3Alyfa .rough-node .label,#mermaid-svg-LCHuOgwKUS3Alyfa .node .label,#mermaid-svg-LCHuOgwKUS3Alyfa .image-shape .label,#mermaid-svg-LCHuOgwKUS3Alyfa .icon-shape .label{text-align:center;}#mermaid-svg-LCHuOgwKUS3Alyfa .node.clickable{cursor:pointer;}#mermaid-svg-LCHuOgwKUS3Alyfa .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-LCHuOgwKUS3Alyfa .arrowheadPath{fill:#333333;}#mermaid-svg-LCHuOgwKUS3Alyfa .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-LCHuOgwKUS3Alyfa .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-LCHuOgwKUS3Alyfa .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-LCHuOgwKUS3Alyfa .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-LCHuOgwKUS3Alyfa .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-LCHuOgwKUS3Alyfa .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-LCHuOgwKUS3Alyfa .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-LCHuOgwKUS3Alyfa .cluster text{fill:#333;}#mermaid-svg-LCHuOgwKUS3Alyfa .cluster span{color:#333;}#mermaid-svg-LCHuOgwKUS3Alyfa div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-LCHuOgwKUS3Alyfa .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-LCHuOgwKUS3Alyfa rect.text{fill:none;stroke-width:0;}#mermaid-svg-LCHuOgwKUS3Alyfa .icon-shape,#mermaid-svg-LCHuOgwKUS3Alyfa .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-LCHuOgwKUS3Alyfa .icon-shape p,#mermaid-svg-LCHuOgwKUS3Alyfa .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-LCHuOgwKUS3Alyfa .icon-shape .label rect,#mermaid-svg-LCHuOgwKUS3Alyfa .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-LCHuOgwKUS3Alyfa .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-LCHuOgwKUS3Alyfa .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-LCHuOgwKUS3Alyfa :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Commit C2
父提交 C1
根 Tree
Blob: README.md
Tree: src
Blob: UserService.java

普通提交通常只有一个父提交,合并提交可能有两个或更多父提交。

4.4 Tag 对象

附注标签,也就是 Annotated Tag,会创建独立的 Tag 对象,其中可以保存:

  • 标签名称;
  • 标签说明;
  • 创建者;
  • 创建时间;
  • 签名信息;
  • 指向的提交或其他对象。

4.5 Git 对象为什么通常不可变

Git 对象的 ID 由对象类型和内容计算得出。

只要对象内容改变,对象 ID 就会改变。因此,已有提交不会被直接修改。所谓"修改提交历史",本质上通常是创建一组新的提交对象,并让分支指向新的提交。

4.6 Branch 的本质

Git 分支本质上是一个可以移动的引用,它通常指向某个提交。

例如:

text 复制代码
main -> C3

当在 main 分支上创建新提交 C4 后:

text 复制代码
main -> C4 -> C3

创建分支并不会复制完整代码,只是创建了一个新的引用,因此 Git 分支非常轻量。

4.7 HEAD 的作用

HEAD 表示当前检出的提交位置。

正常情况下,HEAD 间接指向当前分支:

text 复制代码
HEAD -> main -> C4

如果直接检出某个提交,则会进入 Detached HEAD 状态:

text 复制代码
HEAD -> C2
main -> C4

Detached HEAD 并不代表仓库损坏,但如果在此状态下创建提交,应及时创建分支保存它:

bash 复制代码
git switch -c recovery-branch

5. 安装和配置 Git

5.1 检查 Git 是否安装

bash 复制代码
git --version

如果输出类似下面的内容,说明 Git 已安装:

text 复制代码
git version 2.x.x

5.2 Windows 安装

Windows 用户可以从 Git 官网下载安装 Git for Windows:

text 复制代码
https://git-scm.com/download/win

安装完成后,可以使用:

  • Git Bash;
  • PowerShell;
  • Windows Terminal;
  • IntelliJ IDEA 内置终端;
  • Visual Studio Code 终端。

5.3 macOS 安装

bash 复制代码
brew install git

也可以通过 Xcode Command Line Tools 安装:

bash 复制代码
xcode-select --install

5.4 Linux 安装

Ubuntu 或 Debian:

bash 复制代码
sudo apt update
sudo apt install git

CentOS、Rocky Linux 或 AlmaLinux:

bash 复制代码
sudo dnf install git

5.5 配置用户名和邮箱

bash 复制代码
git config --global user.name "Your Name"
git config --global user.email "you@example.com"

这些信息会写入提交记录。

查看全局配置:

bash 复制代码
git config --global --list

查看所有配置及来源:

bash 复制代码
git config --list --show-origin

5.6 全局配置与仓库配置

Git 配置主要分为三个级别:

级别 参数 影响范围
系统级 --system 当前计算机所有用户
用户级 --global 当前用户的所有仓库
仓库级 --local 当前仓库

仓库级配置的优先级高于用户级配置。

只给当前仓库设置身份:

bash 复制代码
git config --local user.name "Project User"
git config --local user.email "project@example.com"

5.7 设置默认分支名称

bash 复制代码
git config --global init.defaultBranch main

以后执行 git init 时,默认分支会命名为 main

5.8 配置换行符

Windows 常见配置:

bash 复制代码
git config --global core.autocrlf true

macOS 和 Linux 常见配置:

bash 复制代码
git config --global core.autocrlf input

团队项目更推荐使用 .gitattributes 明确指定换行规则,例如:

gitattributes 复制代码
* text=auto
*.sh text eol=lf
*.bat text eol=crlf
*.cmd text eol=crlf

5.9 设置命令别名

bash 复制代码
git config --global alias.st status
git config --global alias.co checkout
git config --global alias.br branch
git config --global alias.ci commit

配置图形化日志:

bash 复制代码
git config --global alias.lg "log --graph --decorate --oneline --all"

之后可以执行:

bash 复制代码
git lg

6. 创建或克隆 Git 仓库

6.1 初始化新仓库

bash 复制代码
mkdir git-demo
cd git-demo
git init

执行完成后,当前目录中会出现 .git 目录。

检查仓库状态:

bash 复制代码
git status

6.2 在当前目录初始化

bash 复制代码
git init

6.3 指定初始分支名称

bash 复制代码
git init -b main

6.4 克隆远程仓库

HTTPS:

bash 复制代码
git clone https://github.com/example/project.git

SSH:

bash 复制代码
git clone git@github.com:example/project.git

指定本地目录名称:

bash 复制代码
git clone https://github.com/example/project.git my-project

只克隆指定分支:

bash 复制代码
git clone --branch develop --single-branch https://github.com/example/project.git

浅克隆:

bash 复制代码
git clone --depth 1 https://github.com/example/project.git

浅克隆只获取有限历史,适合临时构建或减少下载量,但不适合所有历史分析场景。

6.5 git initgit clone 的区别

  • git init:将当前目录初始化为新仓库;
  • git clone:复制已有远程仓库,并自动配置远程地址。

克隆完成后,可以查看远程地址:

bash 复制代码
git remote -v

7. Git 中文件的生命周期

Git 中文件常见状态包括:

  • Untracked:未跟踪;
  • Unmodified:未修改;
  • Modified:已修改;
  • Staged:已暂存;
  • Committed:已提交。

#mermaid-svg-ZvW70EUgWsHHZSVZ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ZvW70EUgWsHHZSVZ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ZvW70EUgWsHHZSVZ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ZvW70EUgWsHHZSVZ .error-icon{fill:#552222;}#mermaid-svg-ZvW70EUgWsHHZSVZ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ZvW70EUgWsHHZSVZ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ZvW70EUgWsHHZSVZ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ZvW70EUgWsHHZSVZ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ZvW70EUgWsHHZSVZ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ZvW70EUgWsHHZSVZ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ZvW70EUgWsHHZSVZ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ZvW70EUgWsHHZSVZ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ZvW70EUgWsHHZSVZ .marker.cross{stroke:#333333;}#mermaid-svg-ZvW70EUgWsHHZSVZ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ZvW70EUgWsHHZSVZ p{margin:0;}#mermaid-svg-ZvW70EUgWsHHZSVZ defs #statediagram-barbEnd{fill:#333333;stroke:#333333;}#mermaid-svg-ZvW70EUgWsHHZSVZ g.stateGroup text{fill:#9370DB;stroke:none;font-size:10px;}#mermaid-svg-ZvW70EUgWsHHZSVZ g.stateGroup text{fill:#333;stroke:none;font-size:10px;}#mermaid-svg-ZvW70EUgWsHHZSVZ g.stateGroup .state-title{font-weight:bolder;fill:#131300;}#mermaid-svg-ZvW70EUgWsHHZSVZ g.stateGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-ZvW70EUgWsHHZSVZ g.stateGroup line{stroke:#333333;stroke-width:1;}#mermaid-svg-ZvW70EUgWsHHZSVZ .transition{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-ZvW70EUgWsHHZSVZ .stateGroup .composit{fill:white;border-bottom:1px;}#mermaid-svg-ZvW70EUgWsHHZSVZ .stateGroup .alt-composit{fill:#e0e0e0;border-bottom:1px;}#mermaid-svg-ZvW70EUgWsHHZSVZ .state-note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-ZvW70EUgWsHHZSVZ .state-note text{fill:black;stroke:none;font-size:10px;}#mermaid-svg-ZvW70EUgWsHHZSVZ .stateLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-ZvW70EUgWsHHZSVZ .edgeLabel .label rect{fill:#ECECFF;opacity:0.5;}#mermaid-svg-ZvW70EUgWsHHZSVZ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ZvW70EUgWsHHZSVZ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ZvW70EUgWsHHZSVZ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ZvW70EUgWsHHZSVZ .edgeLabel .label text{fill:#333;}#mermaid-svg-ZvW70EUgWsHHZSVZ .label div .edgeLabel{color:#333;}#mermaid-svg-ZvW70EUgWsHHZSVZ .stateLabel text{fill:#131300;font-size:10px;font-weight:bold;}#mermaid-svg-ZvW70EUgWsHHZSVZ .node circle.state-start{fill:#333333;stroke:#333333;}#mermaid-svg-ZvW70EUgWsHHZSVZ .node .fork-join{fill:#333333;stroke:#333333;}#mermaid-svg-ZvW70EUgWsHHZSVZ .node circle.state-end{fill:#9370DB;stroke:white;stroke-width:1.5;}#mermaid-svg-ZvW70EUgWsHHZSVZ .end-state-inner{fill:white;stroke-width:1.5;}#mermaid-svg-ZvW70EUgWsHHZSVZ .node rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ZvW70EUgWsHHZSVZ .node polygon{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ZvW70EUgWsHHZSVZ #statediagram-barbEnd{fill:#333333;}#mermaid-svg-ZvW70EUgWsHHZSVZ .statediagram-cluster rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ZvW70EUgWsHHZSVZ .cluster-label,#mermaid-svg-ZvW70EUgWsHHZSVZ .nodeLabel{color:#131300;}#mermaid-svg-ZvW70EUgWsHHZSVZ .statediagram-cluster rect.outer{rx:5px;ry:5px;}#mermaid-svg-ZvW70EUgWsHHZSVZ .statediagram-state .divider{stroke:#9370DB;}#mermaid-svg-ZvW70EUgWsHHZSVZ .statediagram-state .title-state{rx:5px;ry:5px;}#mermaid-svg-ZvW70EUgWsHHZSVZ .statediagram-cluster.statediagram-cluster .inner{fill:white;}#mermaid-svg-ZvW70EUgWsHHZSVZ .statediagram-cluster.statediagram-cluster-alt .inner{fill:#f0f0f0;}#mermaid-svg-ZvW70EUgWsHHZSVZ .statediagram-cluster .inner{rx:0;ry:0;}#mermaid-svg-ZvW70EUgWsHHZSVZ .statediagram-state rect.basic{rx:5px;ry:5px;}#mermaid-svg-ZvW70EUgWsHHZSVZ .statediagram-state rect.divider{stroke-dasharray:10,10;fill:#f0f0f0;}#mermaid-svg-ZvW70EUgWsHHZSVZ .note-edge{stroke-dasharray:5;}#mermaid-svg-ZvW70EUgWsHHZSVZ .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-ZvW70EUgWsHHZSVZ .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-ZvW70EUgWsHHZSVZ .statediagram-note text{fill:black;}#mermaid-svg-ZvW70EUgWsHHZSVZ .statediagram-note .nodeLabel{color:black;}#mermaid-svg-ZvW70EUgWsHHZSVZ .statediagram .edgeLabel{color:red;}#mermaid-svg-ZvW70EUgWsHHZSVZ #dependencyStart,#mermaid-svg-ZvW70EUgWsHHZSVZ #dependencyEnd{fill:#333333;stroke:#333333;stroke-width:1;}#mermaid-svg-ZvW70EUgWsHHZSVZ .statediagramTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ZvW70EUgWsHHZSVZ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 创建新文件
git add
修改文件
git add
git commit
暂存后再次修改
git restore
Untracked
Staged
Unmodified
Modified

7.1 查看状态

bash 复制代码
git status

简洁模式:

bash 复制代码
git status --short

可能看到:

text 复制代码
?? new-file.txt
 M README.md
M  src/App.java
MM src/UserService.java

含义如下:

  • ??:未跟踪文件;
  • 左侧 M:暂存区中的版本已修改;
  • 右侧 M:工作区中的版本已修改;
  • MM:文件暂存后又被继续修改。

7.2 跟踪新文件

bash 复制代码
git add README.md

添加当前目录所有变化:

bash 复制代码
git add .

添加仓库内所有新增、修改和删除:

bash 复制代码
git add -A

7.3 交互式暂存

bash 复制代码
git add -p

该命令可以按代码片段选择需要暂存的内容,适合一个文件中同时包含多个逻辑修改的情况。

7.4 删除文件

同时从工作区和 Git 中删除:

bash 复制代码
git rm old-file.txt

只取消 Git 跟踪,但保留本地文件:

bash 复制代码
git rm --cached application-local.yml

7.5 移动或重命名文件

bash 复制代码
git mv old-name.txt new-name.txt

Git 并不会在对象中永久记录"重命名事件"。很多情况下,Git 会根据内容相似度在查看差异时推断重命名。


8. 暂存区与提交

8.1 为什么需要暂存区

暂存区让开发者可以控制一次提交的边界。

例如,你同时完成了:

  • 用户查询接口;
  • 日志格式调整;
  • 本地配置修改。

如果把它们一次性提交,后续审查、回滚和问题定位都会更加困难。借助暂存区,可以拆分成多个逻辑清晰的提交。

8.2 查看未暂存内容

bash 复制代码
git diff

该命令比较:

text 复制代码
工作区 ↔ 暂存区

8.3 查看已暂存内容

bash 复制代码
git diff --staged

也可以使用:

bash 复制代码
git diff --cached

该命令比较:

text 复制代码
暂存区 ↔ HEAD

8.4 创建提交

bash 复制代码
git commit -m "feat: add user query API"

8.5 打开编辑器编写提交说明

bash 复制代码
git commit

推荐的提交说明格式:

text 复制代码
feat(user): add user query API

Add pagination and status filtering for the user list.
Keep the existing response structure for compatibility.

8.6 修改最近一次提交

如果提交后发现遗漏了一个文件:

bash 复制代码
git add missing-file.txt
git commit --amend --no-edit

如果需要同时修改提交说明:

bash 复制代码
git commit --amend

--amend 会创建一个新的提交来替换原提交,因此提交 ID 会发生变化。

如果原提交已经推送到共享分支,修改前应先确认团队规则,避免随意改写公共历史。

8.7 提交前检查

bash 复制代码
git status
git diff
git diff --staged

如果项目支持自动化测试,还应执行对应测试,例如:

bash 复制代码
mvn test

或者:

bash 复制代码
npm test

一个提交最好只表达一个完整、独立的逻辑变更。


9. 查看提交历史和代码差异

9.1 查看提交历史

bash 复制代码
git log

单行显示:

bash 复制代码
git log --oneline

显示分支图:

bash 复制代码
git log --graph --decorate --oneline --all

查看最近五条记录:

bash 复制代码
git log -5

查看某个文件的历史:

bash 复制代码
git log -- README.md

跟踪文件重命名前的历史:

bash 复制代码
git log --follow -- README.md

9.2 按作者查询

bash 复制代码
git log --author="Alice"

9.3 按提交说明查询

bash 复制代码
git log --grep="login"

9.4 按时间查询

bash 复制代码
git log --since="2026-08-01" --until="2026-08-31"

9.5 查看某次提交

bash 复制代码
git show <commit-id>

只查看提交基本信息:

bash 复制代码
git show --stat <commit-id>

查看某个提交中的文件:

bash 复制代码
git show <commit-id>:src/App.java

9.6 比较两个提交

bash 复制代码
git diff <commit-a> <commit-b>

比较两个分支:

bash 复制代码
git diff main feature/login

查看两个分支从共同祖先开始的变化:

bash 复制代码
git diff main...feature/login

9.7 查看分支各自拥有的提交

bash 复制代码
git log --left-right --graph --oneline main...feature/login

9.8 理解双点和三点

bash 复制代码
git log A..B

表示可从 B 到达、但不可从 A 到达的提交。

bash 复制代码
git log A...B

表示只属于 A 或只属于 B,但不同时属于两者的提交集合。

需要注意,git log A...Bgit diff A...B 的语义并不完全相同。复杂场景下应结合命令文档确认。


10. 使用 .gitignore 忽略文件

.gitignore 用于指定不需要纳入版本控制的文件。

例如:

gitignore 复制代码
# 编译输出
target/
build/
dist/

# 依赖目录
node_modules/

# IDE 配置
.idea/
.vscode/
*.iml

# 日志
*.log
logs/

# 操作系统文件
.DS_Store
Thumbs.db

# 本地配置
.env
application-local.yml

# 临时文件
*.tmp
*.swp

10.1 常见匹配规则

gitignore 复制代码
*.log

忽略所有 .log 文件。

gitignore 复制代码
logs/

忽略 logs 目录。

gitignore 复制代码
/config/local.yml

只忽略仓库根目录下的指定文件。

gitignore 复制代码
!important.log

即使前面的规则忽略了日志文件,也尝试保留 important.log

gitignore 复制代码
temp/**

忽略 temp 目录下的内容。

10.2 .gitignore 为什么没有生效

.gitignore 只影响尚未被跟踪的文件。

如果文件已经进入版本控制,需要先取消跟踪:

bash 复制代码
git rm --cached application-local.yml
git commit -m "chore: stop tracking local configuration"

取消跟踪整个目录:

bash 复制代码
git rm -r --cached target

10.3 查看是哪条规则忽略了文件

bash 复制代码
git check-ignore -v application-local.yml

10.4 全局忽略规则

创建一个全局忽略文件,然后执行:

bash 复制代码
git config --global core.excludesFile ~/.gitignore_global

适合放入操作系统或个人工具生成的文件,但项目自身产生的文件仍应写入仓库中的 .gitignore


11. 分支、HEAD 与标签

11.1 查看分支

bash 复制代码
git branch

查看本地和远程分支:

bash 复制代码
git branch -a

查看分支及其上游关系:

bash 复制代码
git branch -vv

11.2 创建分支

bash 复制代码
git branch feature/login

创建并切换:

bash 复制代码
git switch -c feature/login

传统写法:

bash 复制代码
git checkout -b feature/login

在指定提交上创建分支:

bash 复制代码
git switch -c hotfix/payment <commit-id>

11.3 切换分支

bash 复制代码
git switch main

切换前应检查工作区:

bash 复制代码
git status

如果未提交的修改会被目标分支覆盖,Git 通常会阻止切换。

11.4 重命名分支

重命名当前分支:

bash 复制代码
git branch -m new-name

重命名指定分支:

bash 复制代码
git branch -m old-name new-name

11.5 删除分支

安全删除已合并分支:

bash 复制代码
git branch -d feature/login

强制删除:

bash 复制代码
git branch -D feature/login

删除远程分支:

bash 复制代码
git push origin --delete feature/login

11.6 什么是远程跟踪分支

执行:

bash 复制代码
git fetch origin

后,本地可能出现:

text 复制代码
origin/main
origin/develop
origin/feature/login

这些是本地保存的远程状态引用,不是直接运行在服务器上的分支。

例如:

  • main:本地分支;
  • origin/main:本地记录的远程 main 状态;
  • 远程服务器上的 main:真正的远程分支。

11.7 创建标签

轻量标签:

bash 复制代码
git tag v1.0.0

附注标签:

bash 复制代码
git tag -a v1.0.0 -m "Release version 1.0.0"

给指定提交打标签:

bash 复制代码
git tag -a v1.0.0 <commit-id> -m "Release version 1.0.0"

查看标签:

bash 复制代码
git tag
git show v1.0.0

推送单个标签:

bash 复制代码
git push origin v1.0.0

推送全部标签:

bash 复制代码
git push origin --tags

删除本地标签:

bash 复制代码
git tag -d v1.0.0

删除远程标签:

bash 复制代码
git push origin --delete v1.0.0

正式发布版本时,通常推荐使用附注标签。


12. 合并分支与解决冲突

12.1 合并分支

假设要把 feature/login 合并到 main

bash 复制代码
git switch main
git merge feature/login

这里的含义是:

feature/login 的修改合并到当前所在的 main 分支。

12.2 Fast-forward 合并

如果 main 在创建功能分支后没有新提交,Git 可以直接移动 main 指针:

text 复制代码
合并前:

A---B  main
     \
      C---D  feature/login

合并后:

A---B---C---D  main
              feature/login

这种情况不需要创建新的合并提交。

只允许 Fast-forward:

bash 复制代码
git merge --ff-only feature/login

12.3 创建 Merge Commit

如果两个分支都继续产生了提交,Git 通常需要执行三方合并:

text 复制代码
      C---D  feature/login
     /     \
A---B---E---M  main

M 是合并提交,它通常有两个父提交。

即使可以 Fast-forward,也强制创建合并提交:

bash 复制代码
git merge --no-ff feature/login

12.4 什么情况下会产生冲突

当两个分支修改了同一文件中无法自动协调的区域时,可能产生冲突。

冲突标记通常如下:

text 复制代码
<<<<<<< HEAD
当前分支的内容
=======
被合并分支的内容
>>>>>>> feature/login

12.5 解决冲突的标准流程

第一步,查看冲突文件:

bash 复制代码
git status

第二步,打开文件,根据业务要求保留正确内容,并删除冲突标记。

第三步,暂存解决后的文件:

bash 复制代码
git add src/UserService.java

第四步,完成合并:

bash 复制代码
git commit

部分 Git 版本和场景也可以执行:

bash 复制代码
git merge --continue

12.6 放弃合并

bash 复制代码
git merge --abort

该命令会尝试恢复到开始合并之前的状态。

如果合并前工作区本身不干净,恢复可能会更复杂,因此执行合并前最好先提交或暂存本地修改。

12.7 冲突中的三个版本

解决合并冲突时,索引中可能保存三个版本:

  • Stage 1:共同祖先版本;
  • Stage 2:当前分支版本,也就是 ours;
  • Stage 3:被合并分支版本,也就是 theirs。

查看冲突版本:

bash 复制代码
git ls-files -u

在普通 merge 场景中选择当前分支版本:

bash 复制代码
git checkout --ours path/to/file

选择被合并分支版本:

bash 复制代码
git checkout --theirs path/to/file

然后暂存:

bash 复制代码
git add path/to/file

不要在没有理解业务逻辑的情况下机械选择 ours 或 theirs。


13. Rebase 变基

13.1 Rebase 是什么

Rebase 会把一组提交重新应用到另一个基点上。

假设当前历史如下:

text 复制代码
      C---D  feature
     /
A---B---E---F  main

feature 分支执行:

bash 复制代码
git rebase main

可能得到:

text 复制代码
A---B---E---F  main
             \
              C'---D'  feature

C'D' 是重新创建的新提交,因此提交 ID 与原来的 CD 不同。

13.2 Rebase 的优点

  • 提交历史更加线性;
  • 功能分支可以基于最新主分支整理;
  • 减少不必要的合并提交;
  • 便于代码审查和历史阅读。

13.3 Rebase 的风险

Rebase 会改写提交历史。

不要随意对多人共享、已经公开使用的分支执行 Rebase,否则其他协作者的提交关系可能被打乱。

一个常见原则是:

可以整理尚未公开的个人分支,不要随意改写公共共享历史。

13.4 Rebase 冲突处理

bash 复制代码
git rebase main

发生冲突后:

bash 复制代码
git status

修改文件并暂存:

bash 复制代码
git add conflict-file.txt

继续:

bash 复制代码
git rebase --continue

跳过当前提交:

bash 复制代码
git rebase --skip

放弃整个 Rebase:

bash 复制代码
git rebase --abort

13.5 交互式 Rebase

整理最近三个提交:

bash 复制代码
git rebase -i HEAD~3

常见操作:

操作 含义
pick 保留提交
reword 修改提交说明
edit 暂停并修改提交
squash 合并到前一个提交,并编辑说明
fixup 合并到前一个提交,丢弃当前说明
drop 删除提交

交互式 Rebase 适合在推送代码前整理个人提交历史。

13.6 Merge 和 Rebase 如何选择

场景 推荐方式
个人功能分支同步最新主分支 可以使用 Rebase
合并公共长期分支 通常使用 Merge
需要保留真实分支拓扑 Merge
希望历史保持线性 Rebase 或 Squash Merge
提交已经被多人基于其继续开发 避免 Rebase

没有一种方式适用于所有团队,关键是团队统一规则。


14. 远程仓库与多人协作

14.1 查看远程仓库

bash 复制代码
git remote -v

14.2 添加远程仓库

bash 复制代码
git remote add origin git@github.com:example/project.git

14.3 修改远程地址

bash 复制代码
git remote set-url origin git@github.com:example/new-project.git

14.4 查看远程详细信息

bash 复制代码
git remote show origin

14.5 Fetch:只获取,不自动合并

bash 复制代码
git fetch origin

git fetch 会更新远程跟踪分支,例如 origin/main,但不会直接修改当前分支中的代码。

获取后可以先检查:

bash 复制代码
git log --graph --oneline --decorate main..origin/main
git diff main...origin/main

确认后再合并:

bash 复制代码
git merge origin/main

14.6 Pull:获取并整合

bash 复制代码
git pull

git pull 通常相当于先执行 git fetch,再根据配置执行 Merge 或 Rebase。

明确使用 Merge:

bash 复制代码
git pull --no-rebase

明确使用 Rebase:

bash 复制代码
git pull --rebase

只接受 Fast-forward:

bash 复制代码
git pull --ff-only

为了减少意外合并,很多团队会使用:

bash 复制代码
git config --global pull.ff only

这不是所有团队的唯一正确选择,应根据项目工作流决定。

14.7 Push:推送本地提交

bash 复制代码
git push origin main

第一次推送并建立上游关系:

bash 复制代码
git push -u origin feature/login

建立上游后,可以直接执行:

bash 复制代码
git push
git pull

14.8 为什么 Push 会被拒绝

常见提示:

text 复制代码
rejected
non-fast-forward

通常说明远程分支包含本地没有的提交。

可以先执行:

bash 复制代码
git fetch origin
git log --graph --oneline --decorate --all

然后根据团队规则选择:

bash 复制代码
git merge origin/main

或者:

bash 复制代码
git rebase origin/main

处理完成后再推送。

14.9 强制推送

普通强制推送:

bash 复制代码
git push --force

该命令可能覆盖他人的远程提交,风险很高。

相对更安全的方式是:

bash 复制代码
git push --force-with-lease

--force-with-lease 会在远程分支状态与本地预期不一致时拒绝覆盖,但它仍然属于改写远程历史的操作,使用前必须确认分支范围和团队规则。

14.10 一个仓库配置多个远程地址

bash 复制代码
git remote add origin git@github.com:your-name/project.git
git remote add upstream git@github.com:source-org/project.git

常见于 Fork 工作流:

  • origin:自己的仓库;
  • upstream:原始仓库。

同步上游:

bash 复制代码
git fetch upstream
git switch main
git merge --ff-only upstream/main
git push origin main

15. 撤销修改、回退版本与恢复数据

Git 撤销操作最容易出错。执行前必须先判断:

  1. 修改是否已经暂存;
  2. 修改是否已经提交;
  3. 提交是否已经推送;
  4. 分支是否由多人共享;
  5. 本地修改是否还有保留价值。

15.1 撤销工作区修改

恢复单个文件到暂存区版本:

bash 复制代码
git restore README.md

恢复所有工作区修改:

bash 复制代码
git restore .

这会丢弃未暂存修改,执行前应确认这些内容不再需要。

15.2 取消暂存

bash 复制代码
git restore --staged README.md

该命令只把文件移出暂存区,不会删除工作区修改。

传统写法:

bash 复制代码
git reset HEAD README.md

15.3 恢复指定提交中的文件

bash 复制代码
git restore --source=<commit-id> path/to/file

如果希望把该版本同时放入暂存区:

bash 复制代码
git restore --source=<commit-id> --staged --worktree path/to/file

15.4 撤销最近一次提交但保留暂存内容

bash 复制代码
git reset --soft HEAD~1

结果:

  • 分支回到前一个提交;
  • 修改仍保留在暂存区;
  • 工作区内容不变。

15.5 撤销最近一次提交并取消暂存

bash 复制代码
git reset HEAD~1

默认模式是 --mixed

结果:

  • 分支回到前一个提交;
  • 暂存区被重置;
  • 修改仍保留在工作区。

15.6 丢弃提交和本地修改

bash 复制代码
git reset --hard HEAD~1

该命令会同时重置:

  • HEAD;
  • 暂存区;
  • 工作区。

这是高风险操作,未提交的修改可能直接丢失。

15.7 reset 三种常见模式

命令 移动 HEAD 重置暂存区 重置工作区
git reset --soft
git reset --mixed
git reset --hard

15.8 撤销已经公开的提交

如果错误提交已经推送到共享分支,通常优先使用:

bash 复制代码
git revert <commit-id>

git revert 不删除原提交,而是创建一个新的反向提交。

撤销最近一次提交:

bash 复制代码
git revert HEAD

这种方式保留完整历史,更适合公共分支。

15.9 Reset 和 Revert 的区别

对比项 Reset Revert
是否移动分支指针
是否创建新提交 通常否
是否改写历史
适合本地未公开提交 可以但不一定必要
适合共享分支 通常不推荐 推荐

15.10 使用 Reflog 恢复误删提交

Reflog 会记录本地引用曾经指向的位置。

查看记录:

bash 复制代码
git reflog

可能看到:

text 复制代码
8c31a4d HEAD@{0}: reset: moving to HEAD~1
af42e91 HEAD@{1}: commit: feat: add payment API

恢复误删提交:

bash 复制代码
git switch -c recovery af42e91

或者确认无误后重置当前分支:

bash 复制代码
git reset --hard af42e91

更稳妥的做法是先创建恢复分支,检查内容后再决定如何合并。

Reflog 通常是本地记录,不应把它当作永久备份。记录可能因过期清理而消失。

15.11 清理未跟踪文件

预览将被删除的文件:

bash 复制代码
git clean -n

删除未跟踪文件:

bash 复制代码
git clean -f

预览未跟踪文件和目录:

bash 复制代码
git clean -nd

删除未跟踪文件和目录:

bash 复制代码
git clean -fd

git clean 删除的内容通常没有进入 Git 对象数据库,恢复难度很高。因此必须先使用 -n 预览。


16. Stash、Cherry-pick 与临时开发

16.1 Stash 的作用

当功能尚未完成,但需要临时切换分支处理紧急问题时,可以暂存当前修改:

bash 复制代码
git stash push -m "WIP: user import"

查看 Stash:

bash 复制代码
git stash list

恢复最近一次 Stash,并保留记录:

bash 复制代码
git stash apply

恢复并删除记录:

bash 复制代码
git stash pop

恢复指定记录:

bash 复制代码
git stash apply stash@{1}

删除指定记录:

bash 复制代码
git stash drop stash@{1}

清空所有 Stash:

bash 复制代码
git stash clear

16.2 暂存未跟踪文件

默认情况下,未跟踪文件通常不会进入 Stash。

包含未跟踪文件:

bash 复制代码
git stash push -u -m "WIP: include untracked files"

包含被忽略文件:

bash 复制代码
git stash push -a -m "WIP: include all files"

16.3 从 Stash 创建分支

bash 复制代码
git stash branch feature/recovery stash@{0}

该命令适合 Stash 与当前代码差异过大、直接恢复容易冲突的场景。

16.4 Cherry-pick 的作用

git cherry-pick 可以把某个已有提交引入当前分支。

bash 复制代码
git cherry-pick <commit-id>

常见场景:

  • 把修复提交同步到发布分支;
  • 从错误分支取回某个提交;
  • 只选择另一个分支中的部分提交。

16.5 Cherry-pick 多个提交

依次选择多个提交:

bash 复制代码
git cherry-pick <commit-a> <commit-b>

选择一个提交范围:

bash 复制代码
git cherry-pick <start-commit>^..<end-commit>

16.6 处理 Cherry-pick 冲突

解决冲突后:

bash 复制代码
git add conflict-file.txt
git cherry-pick --continue

跳过当前提交:

bash 复制代码
git cherry-pick --skip

放弃整个操作:

bash 复制代码
git cherry-pick --abort

16.7 Cherry-pick 的注意事项

Cherry-pick 会创建新的提交,因此提交 ID 会改变。

如果大量依赖 Cherry-pick 在分支之间同步代码,可能出现:

  • 重复提交;
  • 历史关系难以理解;
  • 后续合并冲突;
  • 修复遗漏到某些分支。

它适合有明确目标的单个或少量提交,不应替代正常的分支合并流程。


17. 使用 Bisect、Blame 和 Grep 定位问题

17.1 使用 git blame 查找代码来源

bash 复制代码
git blame src/UserService.java

查看指定行:

bash 复制代码
git blame -L 20,40 src/UserService.java

git blame 可以显示:

  • 每一行对应的提交;
  • 作者;
  • 提交时间;
  • 行号。

但它只能告诉你"最后一次修改这行的是谁",不能直接证明 Bug 责任。还应结合提交上下文和业务背景分析。

17.2 使用 git grep 搜索代码

bash 复制代码
git grep "UserService"

显示行号:

bash 复制代码
git grep -n "UserService"

在指定提交中搜索:

bash 复制代码
git grep "UserService" <commit-id>

17.3 使用 git bisect 二分定位问题

当你知道:

  • 当前版本有问题;
  • 某个历史版本没有问题;

可以使用二分查找定位引入问题的提交。

开始:

bash 复制代码
git bisect start

标记当前版本为坏版本:

bash 复制代码
git bisect bad

标记一个正常版本:

bash 复制代码
git bisect good <good-commit-id>

Git 会检出中间提交。测试后继续标记:

bash 复制代码
git bisect good

或者:

bash 复制代码
git bisect bad

最终 Git 会定位最可能首次引入问题的提交。

结束并恢复:

bash 复制代码
git bisect reset

17.4 自动执行 Bisect

如果有可自动判断成功或失败的测试脚本:

bash 复制代码
git bisect run ./test.sh

约定通常是:

  • 返回码 0:正常;
  • 返回码 1127 中除特殊保留值外:失败;
  • 返回码 125:当前提交无法测试,跳过。

自动 Bisect 对定位历史回归问题非常高效。


18. Worktree、Submodule 与 Git LFS

18.1 Git Worktree

普通情况下,一个 Git 仓库目录只能同时检出一个分支。

使用 Worktree,可以为同一个仓库创建多个工作目录:

bash 复制代码
git worktree add ../project-hotfix hotfix/payment

查看 Worktree:

bash 复制代码
git worktree list

删除 Worktree:

bash 复制代码
git worktree remove ../project-hotfix

清理无效记录:

bash 复制代码
git worktree prune

常见用途:

  • 当前功能开发不想中断,同时修复生产问题;
  • 同时运行不同版本;
  • 比较两个分支;
  • 在独立目录中执行构建或测试。

18.2 Git Submodule

Submodule 用于在一个 Git 仓库中引用另一个独立 Git 仓库的特定提交。

添加子模块:

bash 复制代码
git submodule add git@github.com:example/common.git common

克隆包含子模块的仓库:

bash 复制代码
git clone --recurse-submodules git@github.com:example/project.git

初始化已有子模块:

bash 复制代码
git submodule update --init --recursive

更新子模块到父仓库记录的提交:

bash 复制代码
git submodule update --recursive

父仓库记录的是子模块提交 ID,而不是子模块所有文件的普通副本。

修改子模块时,需要分别考虑:

  1. 子模块仓库中的提交;
  2. 父仓库中的 Gitlink 更新;
  3. 子模块远程分支;
  4. 团队成员是否能访问子模块远程地址。

18.3 Git LFS

Git 不适合频繁管理大型二进制文件,例如:

  • 大型视频;
  • PSD 文件;
  • 模型文件;
  • 大型压缩包;
  • 游戏资源;
  • 数据集。

Git LFS 会在普通 Git 历史中保存指针,并将真实大文件存储在 LFS 服务中。

安装后初始化:

bash 复制代码
git lfs install

跟踪文件类型:

bash 复制代码
git lfs track "*.psd"
git lfs track "*.zip"

这会修改 .gitattributes,应把它一并提交:

bash 复制代码
git add .gitattributes
git commit -m "chore: track large assets with Git LFS"

Git LFS 不是 Git 核心对象存储的完全替代品,还需要远程平台提供相应支持和配额。


19. 常见 Git 工作流

19.1 Feature Branch 工作流

每个功能创建独立分支:

text 复制代码
main
 ├── feature/login
 ├── feature/payment
 └── fix/user-cache

基本流程:

bash 复制代码
git switch main
git pull --ff-only
git switch -c feature/login

# 开发并提交
git add .
git commit -m "feat(auth): add password login"

git push -u origin feature/login

然后创建 Pull Request 或 Merge Request。

适合:

  • 大多数团队项目;
  • 需要代码审查;
  • 功能相对独立;
  • 有持续集成检查。

19.2 GitHub Flow

GitHub Flow 通常围绕一个可随时发布的主分支展开:

  1. main 创建短期分支;
  2. 开发并持续推送;
  3. 创建 Pull Request;
  4. 自动测试和代码审查;
  5. 合并到 main
  6. 部署;
  7. 删除功能分支。

它适合持续交付和频繁发布的项目。

19.3 Git Flow

Git Flow 常见分支包括:

  • main:正式发布版本;
  • develop:日常开发集成;
  • feature/*:功能分支;
  • release/*:发布准备分支;
  • hotfix/*:生产紧急修复分支。

示意图:
#mermaid-svg-BpHqzsVgJhOJ1kYP{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-BpHqzsVgJhOJ1kYP .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-BpHqzsVgJhOJ1kYP .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-BpHqzsVgJhOJ1kYP .error-icon{fill:#552222;}#mermaid-svg-BpHqzsVgJhOJ1kYP .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-BpHqzsVgJhOJ1kYP .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-BpHqzsVgJhOJ1kYP .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-BpHqzsVgJhOJ1kYP .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-BpHqzsVgJhOJ1kYP .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-BpHqzsVgJhOJ1kYP .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-BpHqzsVgJhOJ1kYP .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-BpHqzsVgJhOJ1kYP .marker{fill:#333333;stroke:#333333;}#mermaid-svg-BpHqzsVgJhOJ1kYP .marker.cross{stroke:#333333;}#mermaid-svg-BpHqzsVgJhOJ1kYP svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-BpHqzsVgJhOJ1kYP p{margin:0;}#mermaid-svg-BpHqzsVgJhOJ1kYP .commit-id,#mermaid-svg-BpHqzsVgJhOJ1kYP .commit-msg,#mermaid-svg-BpHqzsVgJhOJ1kYP .branch-label{fill:lightgrey;color:lightgrey;font-family:'trebuchet ms',verdana,arial,sans-serif;font-family:var(--mermaid-font-family);}#mermaid-svg-BpHqzsVgJhOJ1kYP .branch-label0{fill:#ffffff;}#mermaid-svg-BpHqzsVgJhOJ1kYP .commit0{stroke:hsl(240, 100%, 46.2745098039%);fill:hsl(240, 100%, 46.2745098039%);}#mermaid-svg-BpHqzsVgJhOJ1kYP .commit-highlight0{stroke:hsl(60, 100%, 3.7254901961%);fill:hsl(60, 100%, 3.7254901961%);}#mermaid-svg-BpHqzsVgJhOJ1kYP .label0{fill:hsl(240, 100%, 46.2745098039%);}#mermaid-svg-BpHqzsVgJhOJ1kYP .arrow0{stroke:hsl(240, 100%, 46.2745098039%);}#mermaid-svg-BpHqzsVgJhOJ1kYP .branch-label1{fill:black;}#mermaid-svg-BpHqzsVgJhOJ1kYP .commit1{stroke:hsl(60, 100%, 43.5294117647%);fill:hsl(60, 100%, 43.5294117647%);}#mermaid-svg-BpHqzsVgJhOJ1kYP .commit-highlight1{stroke:rgb(0, 0, 160.5);fill:rgb(0, 0, 160.5);}#mermaid-svg-BpHqzsVgJhOJ1kYP .label1{fill:hsl(60, 100%, 43.5294117647%);}#mermaid-svg-BpHqzsVgJhOJ1kYP .arrow1{stroke:hsl(60, 100%, 43.5294117647%);}#mermaid-svg-BpHqzsVgJhOJ1kYP .branch-label2{fill:black;}#mermaid-svg-BpHqzsVgJhOJ1kYP .commit2{stroke:hsl(80, 100%, 46.2745098039%);fill:hsl(80, 100%, 46.2745098039%);}#mermaid-svg-BpHqzsVgJhOJ1kYP .commit-highlight2{stroke:rgb(48.8333333334, 0, 146.5000000001);fill:rgb(48.8333333334, 0, 146.5000000001);}#mermaid-svg-BpHqzsVgJhOJ1kYP .label2{fill:hsl(80, 100%, 46.2745098039%);}#mermaid-svg-BpHqzsVgJhOJ1kYP .arrow2{stroke:hsl(80, 100%, 46.2745098039%);}#mermaid-svg-BpHqzsVgJhOJ1kYP .branch-label3{fill:#ffffff;}#mermaid-svg-BpHqzsVgJhOJ1kYP .commit3{stroke:hsl(210, 100%, 46.2745098039%);fill:hsl(210, 100%, 46.2745098039%);}#mermaid-svg-BpHqzsVgJhOJ1kYP .commit-highlight3{stroke:rgb(146.5000000001, 73.2500000001, 0);fill:rgb(146.5000000001, 73.2500000001, 0);}#mermaid-svg-BpHqzsVgJhOJ1kYP .label3{fill:hsl(210, 100%, 46.2745098039%);}#mermaid-svg-BpHqzsVgJhOJ1kYP .arrow3{stroke:hsl(210, 100%, 46.2745098039%);}#mermaid-svg-BpHqzsVgJhOJ1kYP .branch-label4{fill:black;}#mermaid-svg-BpHqzsVgJhOJ1kYP .commit4{stroke:hsl(180, 100%, 46.2745098039%);fill:hsl(180, 100%, 46.2745098039%);}#mermaid-svg-BpHqzsVgJhOJ1kYP .commit-highlight4{stroke:rgb(146.5000000001, 0, 0);fill:rgb(146.5000000001, 0, 0);}#mermaid-svg-BpHqzsVgJhOJ1kYP .label4{fill:hsl(180, 100%, 46.2745098039%);}#mermaid-svg-BpHqzsVgJhOJ1kYP .arrow4{stroke:hsl(180, 100%, 46.2745098039%);}#mermaid-svg-BpHqzsVgJhOJ1kYP .branch-label5{fill:black;}#mermaid-svg-BpHqzsVgJhOJ1kYP .commit5{stroke:hsl(150, 100%, 46.2745098039%);fill:hsl(150, 100%, 46.2745098039%);}#mermaid-svg-BpHqzsVgJhOJ1kYP .commit-highlight5{stroke:rgb(146.5000000001, 0, 73.2500000001);fill:rgb(146.5000000001, 0, 73.2500000001);}#mermaid-svg-BpHqzsVgJhOJ1kYP .label5{fill:hsl(150, 100%, 46.2745098039%);}#mermaid-svg-BpHqzsVgJhOJ1kYP .arrow5{stroke:hsl(150, 100%, 46.2745098039%);}#mermaid-svg-BpHqzsVgJhOJ1kYP .branch-label6{fill:black;}#mermaid-svg-BpHqzsVgJhOJ1kYP .commit6{stroke:hsl(300, 100%, 46.2745098039%);fill:hsl(300, 100%, 46.2745098039%);}#mermaid-svg-BpHqzsVgJhOJ1kYP .commit-highlight6{stroke:rgb(0, 146.5000000001, 0);fill:rgb(0, 146.5000000001, 0);}#mermaid-svg-BpHqzsVgJhOJ1kYP .label6{fill:hsl(300, 100%, 46.2745098039%);}#mermaid-svg-BpHqzsVgJhOJ1kYP .arrow6{stroke:hsl(300, 100%, 46.2745098039%);}#mermaid-svg-BpHqzsVgJhOJ1kYP .branch-label7{fill:black;}#mermaid-svg-BpHqzsVgJhOJ1kYP .commit7{stroke:hsl(0, 100%, 46.2745098039%);fill:hsl(0, 100%, 46.2745098039%);}#mermaid-svg-BpHqzsVgJhOJ1kYP .commit-highlight7{stroke:rgb(0, 146.5000000001, 146.5000000001);fill:rgb(0, 146.5000000001, 146.5000000001);}#mermaid-svg-BpHqzsVgJhOJ1kYP .label7{fill:hsl(0, 100%, 46.2745098039%);}#mermaid-svg-BpHqzsVgJhOJ1kYP .arrow7{stroke:hsl(0, 100%, 46.2745098039%);}#mermaid-svg-BpHqzsVgJhOJ1kYP .branch{stroke-width:1;stroke:#333333;stroke-dasharray:2;}#mermaid-svg-BpHqzsVgJhOJ1kYP .commit-label{font-size:10px;fill:#000021;}#mermaid-svg-BpHqzsVgJhOJ1kYP .commit-label-bkg{font-size:10px;fill:#ffffde;opacity:0.5;}#mermaid-svg-BpHqzsVgJhOJ1kYP .tag-label{font-size:10px;fill:#131300;}#mermaid-svg-BpHqzsVgJhOJ1kYP .tag-label-bkg{fill:#ECECFF;stroke:hsl(240, 60%, 86.2745098039%);}#mermaid-svg-BpHqzsVgJhOJ1kYP .tag-hole{fill:#333;}#mermaid-svg-BpHqzsVgJhOJ1kYP .commit-merge{stroke:#ECECFF;fill:#ECECFF;}#mermaid-svg-BpHqzsVgJhOJ1kYP .commit-reverse{stroke:#ECECFF;fill:#ECECFF;stroke-width:3;}#mermaid-svg-BpHqzsVgJhOJ1kYP .commit-highlight-inner{stroke:#ECECFF;fill:#ECECFF;}#mermaid-svg-BpHqzsVgJhOJ1kYP .arrow{stroke-width:8;stroke-linecap:round;fill:none;}#mermaid-svg-BpHqzsVgJhOJ1kYP .gitTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-BpHqzsVgJhOJ1kYP :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} main develop feature/login release/1.1.0 v1.0.0 develop login release fix v1.1.0

Git Flow 分支较多,适合有明确版本周期和多环境发布流程的项目,但对于高频持续交付项目可能显得过于复杂。

19.4 Trunk-Based Development

Trunk-Based Development 强调:

  • 围绕一个主干分支开发;
  • 功能分支生命周期很短;
  • 频繁合并;
  • 使用自动化测试保证质量;
  • 未完成功能通过 Feature Flag 隐藏;
  • 减少长期分支。

这种模式对持续集成、自动化测试和小批量提交能力要求较高。

19.5 Fork 工作流

常见于开源项目:

  1. Fork 原始仓库;
  2. 克隆自己的 Fork;
  3. 添加 upstream
  4. 创建功能分支;
  5. 推送到自己的仓库;
  6. 向原仓库提交 Pull Request。
bash 复制代码
git clone git@github.com:your-name/project.git
cd project

git remote add upstream git@github.com:source-org/project.git
git fetch upstream

git switch -c fix/login upstream/main

19.6 如何选择工作流

项目情况 推荐方向
小团队、持续交付 GitHub Flow 或 Trunk-Based
大多数普通研发团队 Feature Branch
明确版本周期、多发布分支 Git Flow 或其简化版本
开源项目 Fork + Pull Request
自动化测试不足 先完善质量门禁,再缩短分支周期

工作流不是越复杂越专业。分支和流程应服务于交付,而不是增加无意义的管理成本。


20. 如何编写高质量 Commit

20.1 一个提交只解决一个问题

不推荐:

text 复制代码
修改用户接口、升级依赖、修复支付问题、调整格式

推荐拆分为:

text 复制代码
feat(user): add user status filter
fix(payment): avoid duplicate refund
build: upgrade Spring Boot version
style: format user controller

20.2 Conventional Commits

常见格式:

text 复制代码
<type>(<scope>): <subject>

完整形式:

text 复制代码
<type>(<scope>): <subject>

<body>

<footer>

常见类型:

类型 含义
feat 新功能
fix Bug 修复
docs 文档修改
style 不影响逻辑的格式调整
refactor 重构
perf 性能优化
test 测试修改
build 构建系统或依赖修改
ci CI 配置修改
chore 其他维护性修改
revert 撤销提交

示例:

text 复制代码
feat(user): add account status filter
text 复制代码
fix(payment): prevent duplicate refund submission

Reject repeated refund requests with the same business ID.

Closes: PAY-1024

20.3 Breaking Change

存在不兼容变更时,可以使用:

text 复制代码
feat(api)!: remove deprecated user endpoint

或者在页脚说明:

text 复制代码
BREAKING CHANGE: `/api/v1/users` has been removed.

20.4 高质量提交的特点

  • 变更范围清晰;
  • 提交说明准确;
  • 不包含无关文件;
  • 可以独立构建或验证;
  • 不提交密码和密钥;
  • 不提交本地临时配置;
  • 不混入大规模无意义格式化;
  • 必要时说明为什么修改,而不仅是修改了什么。

20.5 提交前自查

bash 复制代码
git status
git diff
git diff --staged
git diff --check

然后执行项目测试。

确认暂存内容后再提交:

bash 复制代码
git commit -m "fix(order): avoid duplicate order creation"

21. Git 安全规范与团队最佳实践

21.1 不要提交敏感信息

禁止提交:

  • 数据库密码;
  • API Key;
  • Access Token;
  • SSH 私钥;
  • 云服务凭证;
  • 生产环境配置;
  • 用户隐私数据;
  • 内部证书私钥。

即使之后删除文件,敏感信息仍可能保留在 Git 历史中。

如果密钥已经提交:

  1. 立即吊销或轮换密钥;
  2. 评估泄露范围;
  3. 清理 Git 历史;
  4. 通知相关协作者重新同步;
  5. 增加密钥扫描和提交前检查。

"从最新提交中删除文件"不能代替密钥轮换。

21.2 谨慎执行危险命令

以下命令执行前必须确认影响范围:

bash 复制代码
git reset --hard
git clean -fd
git branch -D
git push --force
git rebase
git filter-branch

优先采用:

  • 先运行 git status
  • 先创建备份分支;
  • git clean 先使用 -n
  • 强制推送优先考虑 --force-with-lease
  • 公共提交优先使用 git revert
  • 使用 git reflog 查找可恢复位置。

创建安全分支:

bash 复制代码
git branch backup-before-rewrite

21.3 不要直接在主分支开发

推荐:

bash 复制代码
git switch main
git pull --ff-only
git switch -c feature/order-query

开发完成后通过 Pull Request 或 Merge Request 合并。

21.4 保持分支生命周期短

长期不合并的分支容易产生:

  • 大量冲突;
  • 重复开发;
  • 集成风险;
  • 测试滞后;
  • 代码审查困难。

更推荐:

  • 小步开发;
  • 频繁同步主分支;
  • 尽早创建 Draft Pull Request;
  • 每次提交保持可审查;
  • 尽快合并并删除分支。

21.5 保护主分支

远程平台通常可以配置:

  • 禁止直接 Push;
  • 必须通过 Pull Request;
  • 必须通过自动化测试;
  • 必须完成代码审查;
  • 必须解决所有讨论;
  • 禁止普通强制推送;
  • 要求提交签名;
  • 要求分支保持最新。

21.6 使用 Hooks 做本地检查

Git Hooks 位于:

text 复制代码
.git/hooks/

常见 Hook:

  • pre-commit:提交前执行;
  • commit-msg:检查提交说明;
  • pre-push:推送前执行;
  • post-merge:合并后执行。

Hooks 可以用于:

  • 格式检查;
  • 静态扫描;
  • 单元测试;
  • Commit Message 校验;
  • 密钥扫描。

需要注意,普通 .git/hooks 内容不会随着 git clone 自动分发。团队通常会使用脚本、构建工具或专门的 Hook 管理方案统一安装。

21.7 避免提交生成物和本地环境文件

通常不应提交:

text 复制代码
target/
build/
node_modules/
.idea/
*.log
.env
application-local.yml

但并非所有生成文件都应该忽略。例如:

  • 依赖锁文件通常应该提交;
  • 数据库迁移脚本应该提交;
  • 构建产物是否提交取决于发布方式;
  • 代码生成结果是否提交取决于团队规范。

22. 一个完整的 Git 实战流程

下面演示从创建仓库到完成一个功能分支的完整过程。

22.1 初始化仓库

bash 复制代码
mkdir git-demo
cd git-demo

git init -b main

22.2 创建项目文件

创建 README.md

markdown 复制代码
# Git Demo

This is a Git demo project.

检查状态:

bash 复制代码
git status

22.3 第一次提交

bash 复制代码
git add README.md
git diff --staged
git commit -m "docs: add project README"

22.4 添加远程仓库

bash 复制代码
git remote add origin git@github.com:example/git-demo.git
git remote -v

22.5 推送主分支

bash 复制代码
git push -u origin main

22.6 创建功能分支

bash 复制代码
git switch -c feature/hello

创建 hello.txt 后执行:

bash 复制代码
git status
git diff
git add hello.txt
git diff --staged
git commit -m "feat: add hello message"

22.7 推送功能分支

bash 复制代码
git push -u origin feature/hello

然后在代码托管平台创建 Pull Request 或 Merge Request。

22.8 同步主分支最新代码

bash 复制代码
git fetch origin
git switch feature/hello
git rebase origin/main

如果团队使用 Merge:

bash 复制代码
git merge origin/main

不要在同一功能分支上混乱地交替使用不同策略,团队应统一规则。

22.9 本地模拟合并检查

bash 复制代码
git switch main
git pull --ff-only
git merge --no-ff feature/hello

运行测试,确认无误。

如果这只是本地模拟、不希望保留合并结果,可以在明确没有需要保留的本地修改时恢复。实际团队协作中通常应通过平台合并,不应随意重写已共享历史。

22.10 删除已合并分支

bash 复制代码
git branch -d feature/hello
git push origin --delete feature/hello

22.11 创建发布标签

bash 复制代码
git tag -a v1.0.0 -m "Release version 1.0.0"
git push origin v1.0.0

最终历史可能类似:

text 复制代码
*   92ac821 (HEAD -> main, tag: v1.0.0) Merge feature/hello
|\
| * 51d26f3 feat: add hello message
|/
* a4173b2 docs: add project README

23. Git 常用命令速查表

23.1 基础配置

bash 复制代码
git --version
git config --global user.name "Your Name"
git config --global user.email "you@example.com"
git config --list --show-origin

23.2 仓库操作

bash 复制代码
git init
git init -b main
git clone <repository-url>
git status

23.3 暂存与提交

bash 复制代码
git add <file>
git add .
git add -A
git add -p
git commit -m "message"
git commit --amend

23.4 查看差异

bash 复制代码
git diff
git diff --staged
git diff HEAD
git diff <commit-a> <commit-b>
git show <commit-id>

23.5 查看历史

bash 复制代码
git log
git log --oneline
git log --graph --decorate --oneline --all
git log --follow -- <file>
git reflog

23.6 分支操作

bash 复制代码
git branch
git branch -a
git branch -vv
git switch <branch>
git switch -c <new-branch>
git branch -m <new-name>
git branch -d <branch>

23.7 合并与变基

bash 复制代码
git merge <branch>
git merge --abort
git rebase <branch>
git rebase --continue
git rebase --abort
git rebase -i HEAD~3

23.8 远程操作

bash 复制代码
git remote -v
git remote add origin <url>
git fetch origin
git pull --ff-only
git push
git push -u origin <branch>
git push origin --delete <branch>

23.9 撤销与恢复

bash 复制代码
git restore <file>
git restore --staged <file>
git reset --soft HEAD~1
git reset HEAD~1
git reset --hard HEAD~1
git revert <commit-id>
git reflog

23.10 临时保存

bash 复制代码
git stash push -m "message"
git stash push -u -m "message"
git stash list
git stash apply
git stash pop
git stash drop

23.11 标签

bash 复制代码
git tag
git tag -a v1.0.0 -m "Release v1.0.0"
git push origin v1.0.0
git push origin --tags

23.12 问题定位

bash 复制代码
git blame <file>
git grep "keyword"
git bisect start
git bisect good <commit>
git bisect bad
git bisect reset

24. 常见问题

24.1 为什么 git add 后还要 git commit

git add 只是把内容放入暂存区,git commit 才会创建正式的本地提交。

text 复制代码
工作区 --git add--> 暂存区 --git commit--> 本地仓库

24.2 为什么 git commit 后 GitHub 上没有变化

因为提交只保存在本地,还需要推送:

bash 复制代码
git push

24.3 为什么 git pull 后出现 Merge Commit

远程分支和本地分支都产生了新提交,Git 根据当前 Pull 配置执行了合并。

如果团队要求线性历史,可以使用:

bash 复制代码
git pull --rebase

如果只接受 Fast-forward:

bash 复制代码
git pull --ff-only

24.4 为什么切换分支失败

常见原因是当前存在未提交修改,并且这些修改会被目标分支覆盖。

先检查:

bash 复制代码
git status

然后选择:

  • 提交修改;
  • 使用 Stash;
  • 确认无用后恢复修改;
  • 创建新分支保留当前工作。

24.5 为什么删除分支时提示未合并

使用:

bash 复制代码
git branch -d feature/test

时,Git 会检查该分支是否已经合并。

如果确实不再需要,可以强制删除:

bash 复制代码
git branch -D feature/test

删除前建议先查看未合并提交:

bash 复制代码
git log main..feature/test

24.6 为什么 .gitignore 不生效

文件可能已经被 Git 跟踪。

可以执行:

bash 复制代码
git rm --cached <file>

然后提交取消跟踪的修改。

24.7 如何找回误删分支

查看 Reflog:

bash 复制代码
git reflog

找到原分支最后一个提交,然后创建恢复分支:

bash 复制代码
git switch -c recovered-branch <commit-id>

24.8 如何撤销已经推送的提交

共享分支通常使用:

bash 复制代码
git revert <commit-id>
git push

不建议直接对公共分支执行 reset 后强制推送。

24.9 origin/main 是远程分支吗

更准确地说,origin/main 是保存在本地的远程跟踪引用,表示本地最近获取到的远程 main 状态。

它只有在执行下面的命令后才会更新:

bash 复制代码
git fetch origin

24.10 checkoutswitchrestore 有什么区别

历史上 git checkout 同时承担:

  • 切换分支;
  • 创建分支;
  • 检出提交;
  • 恢复文件。

为了让语义更清晰,Git 后来提供:

  • git switch:主要负责切换分支;
  • git restore:主要负责恢复文件;
  • git checkout:仍然可用,但承担的功能更多。

新手可以优先使用 switchrestore

24.11 提交越多越好吗

不是。

好的提交应该:

  • 足够小,便于审查和回滚;
  • 又足够完整,不是毫无意义的碎片;
  • 表达一个清晰的逻辑变更;
  • 在合理情况下能够独立构建或验证。

24.12 Git 能替代备份系统吗

不能完全替代。

Git 适合管理版本历史,但仍然可能遇到:

  • 仓库损坏;
  • 本地磁盘故障;
  • 远程仓库误删;
  • Reflog 过期;
  • 未跟踪文件丢失;
  • 敏感数据泄露;
  • 大文件存储限制。

重要项目仍应配置:

  • 远程仓库;
  • 权限控制;
  • 仓库备份;
  • 分支保护;
  • 制品备份;
  • 数据库和运行数据备份。

25. 总结

Git 的命令很多,但核心模型并不复杂:

text 复制代码
工作区 -> 暂存区 -> 本地仓库 -> 远程仓库

学习 Git 时,建议重点掌握以下内容:

  1. 使用 git status 判断当前状态;
  2. 使用 git diff 确认修改内容;
  3. 使用 git add 控制提交范围;
  4. 使用 git commit 保存本地版本;
  5. 使用分支隔离不同功能;
  6. 理解 Merge 和 Rebase 的区别;
  7. 理解 Fetch、Pull 和 Push 的区别;
  8. 公共提交优先使用 Revert 撤销;
  9. 使用 Reflog 恢复误操作;
  10. reset --hardclean -fd 和强制推送保持谨慎;
  11. 保持提交小而完整;
  12. 通过 Pull Request、自动化测试和分支保护保证团队代码质量。

Git 真正困难的地方不是记住所有命令,而是准确判断:

  • 当前修改位于哪个区域;
  • 当前分支指向哪个提交;
  • 本地和远程分别有哪些提交;
  • 操作是否会改写历史;
  • 修改是否已经被其他人使用;
  • 出现问题后是否仍有可恢复路径。

只要理解工作区、暂存区、提交、引用和远程跟踪分支之间的关系,大部分 Git 问题都可以通过下面几条命令逐步分析:

bash 复制代码
git status
git diff
git diff --staged
git branch -vv
git log --graph --decorate --oneline --all
git reflog

在不确定时,先查看状态和历史,再执行修改操作,永远比直接尝试高风险命令更加可靠。


26. 参考资料

相关推荐
JavaDog程序狗2 小时前
【规范】这套 Git 规范,救了整个团队
git·代码规范·workflow
喜乐MI2 小时前
Git 分支操作避坑指南:merge、cherry-pick、revert 与 reset 怎么选?
git·github
fpcc16 小时前
工具使用—git add命令分析说明
git·工具
独隅20 小时前
VS Code Git 工作树多分支并行开发实战指南
大数据·git·elasticsearch
丑过三八线1 天前
Git 合并未合并分支完整教程
git
TELL5212 天前
配置git ignore
java·git
qq_349447952 天前
Linux系统,安装git,从使用git下载仓库(GitHub, Gitee, GitLab 等),并且使用ssh密钥,可以直接执行git pull
linux·git·ssh
凯尔萨厮2 天前
Git(学习笔记)
笔记·git·学习
一技安身2 天前
【Git】零基础入门实战教程(Windows 完整版,适配竞赛仓库)
windows·git